Repository: open-obfuscator/dProtect Branch: main Commit: 25173cc11644 Files: 690 Total size: 4.5 MB Directory structure: gitextract_i4svht9p/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── scripts/ │ │ └── s3-deploy.py │ └── workflows/ │ ├── continuous_integration.yml │ └── main.yml ├── .gitignore ├── .hgignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── annotations/ │ ├── annotations.pro │ ├── build.gradle │ └── src/ │ └── proguard/ │ └── annotation/ │ ├── Keep.java │ ├── KeepApplication.java │ ├── KeepClassMemberNames.java │ ├── KeepClassMembers.java │ ├── KeepGettersSetters.java │ ├── KeepImplementations.java │ ├── KeepName.java │ ├── KeepPublicClassMemberNames.java │ ├── KeepPublicClassMembers.java │ ├── KeepPublicGettersSetters.java │ ├── KeepPublicImplementations.java │ ├── KeepPublicProtectedClassMemberNames.java │ └── KeepPublicProtectedClassMembers.java ├── ant/ │ ├── build.gradle │ └── src/ │ └── proguard/ │ └── ant/ │ ├── ClassPathElement.java │ ├── ClassSpecificationElement.java │ ├── ConfigurationElement.java │ ├── ConfigurationTask.java │ ├── FilterElement.java │ ├── KeepSpecificationElement.java │ ├── MemberSpecificationElement.java │ ├── ProGuardTask.java │ ├── package.html │ └── task.properties ├── base/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── dprotect/ │ │ │ │ ├── ArithmeticObfuscationClassSpecification.java │ │ │ │ ├── CFObfuscationClassSpecification.java │ │ │ │ ├── ClassObfuSpecVisitorFactory.java │ │ │ │ ├── Configuration.java │ │ │ │ ├── ConfigurationConstants.java │ │ │ │ ├── ConfigurationParser.java │ │ │ │ ├── ConstantObfuscationClassSpecification.java │ │ │ │ ├── DProtect.java │ │ │ │ ├── ObfuscationClassSpecification.java │ │ │ │ ├── deobfuscation/ │ │ │ │ │ ├── Deobfuscator.java │ │ │ │ │ └── strings/ │ │ │ │ │ └── XoredStrings.java │ │ │ │ ├── obfuscation/ │ │ │ │ │ ├── CodeObfuscator.java │ │ │ │ │ ├── Marker.java │ │ │ │ │ ├── arithmetic/ │ │ │ │ │ │ ├── ArithmeticObfuscationFilter.java │ │ │ │ │ │ ├── ArithmeticObfuscationInfo.java │ │ │ │ │ │ ├── ArithmeticObfuscationMarker.java │ │ │ │ │ │ ├── MBANormalizer.java │ │ │ │ │ │ ├── MBAObfuscationAdd.java │ │ │ │ │ │ ├── MBAObfuscationAnd.java │ │ │ │ │ │ ├── MBAObfuscationOr.java │ │ │ │ │ │ ├── MBAObfuscationSub.java │ │ │ │ │ │ └── MBAObfuscationXor.java │ │ │ │ │ ├── constants/ │ │ │ │ │ │ ├── ConstantFieldMarker.java │ │ │ │ │ │ ├── ConstantObfuscationInfo.java │ │ │ │ │ │ ├── ConstantObfuscationMarker.java │ │ │ │ │ │ └── ConstantsObfuscator.java │ │ │ │ │ ├── controlflow/ │ │ │ │ │ │ ├── ControlFlowObfuscation.java │ │ │ │ │ │ ├── ControlFlowObfuscationInfo.java │ │ │ │ │ │ └── ControlFlowObfuscationMarker.java │ │ │ │ │ ├── info/ │ │ │ │ │ │ ├── ObfuscationInfo.java │ │ │ │ │ │ └── ObfuscationInfoSetter.java │ │ │ │ │ └── strings/ │ │ │ │ │ ├── StringFieldMarker.java │ │ │ │ │ ├── StringObfuscationMarker.java │ │ │ │ │ └── StringObfuscator.java │ │ │ │ └── runtime/ │ │ │ │ ├── strings/ │ │ │ │ │ └── StringEncoding.java │ │ │ │ └── util/ │ │ │ │ ├── Helpers.java │ │ │ │ └── Loader.java │ │ │ └── proguard/ │ │ │ ├── AfterInitConfigurationVerifier.java │ │ │ ├── AppView.java │ │ │ ├── ArgumentWordReader.java │ │ │ ├── AssumeNoSideEffectsChecker.java │ │ │ ├── ClassMemberChecker.java │ │ │ ├── ClassPath.java │ │ │ ├── ClassPathEntry.java │ │ │ ├── ClassSpecification.java │ │ │ ├── ClassSpecificationVisitorFactory.java │ │ │ ├── Configuration.java │ │ │ ├── ConfigurationConstants.java │ │ │ ├── ConfigurationParser.java │ │ │ ├── ConfigurationVerifier.java │ │ │ ├── ConfigurationWriter.java │ │ │ ├── DataEntryReaderFactory.java │ │ │ ├── DataEntryWriterFactory.java │ │ │ ├── DescriptorKeepChecker.java │ │ │ ├── Dumper.java │ │ │ ├── DuplicateClassPrinter.java │ │ │ ├── DuplicateResourceFilePrinter.java │ │ │ ├── FileWordReader.java │ │ │ ├── FullyQualifiedClassNameChecker.java │ │ │ ├── GPL.java │ │ │ ├── GetAnnotationChecker.java │ │ │ ├── GetEnclosingClassChecker.java │ │ │ ├── GetEnclosingMethodChecker.java │ │ │ ├── GetSignatureChecker.java │ │ │ ├── Initializer.java │ │ │ ├── InputReader.java │ │ │ ├── KeepClassMemberChecker.java │ │ │ ├── KeepClassSpecification.java │ │ │ ├── KeepClassSpecificationVisitorFactory.java │ │ │ ├── KotlinMetadataAdapter.java │ │ │ ├── LibraryKeepChecker.java │ │ │ ├── LineWordReader.java │ │ │ ├── MemberSpecification.java │ │ │ ├── MemberValueSpecification.java │ │ │ ├── OutputWriter.java │ │ │ ├── ParseException.java │ │ │ ├── ProGuard.java │ │ │ ├── SeedPrinter.java │ │ │ ├── SubclassedClassFilter.java │ │ │ ├── Targeter.java │ │ │ ├── UpToDateChecker.java │ │ │ ├── WordReader.java │ │ │ ├── backport/ │ │ │ │ ├── AbstractAPIConverter.java │ │ │ │ ├── Backporter.java │ │ │ │ ├── DefaultInterfaceMethodConverter.java │ │ │ │ ├── JSR310Converter.java │ │ │ │ ├── LambdaExpression.java │ │ │ │ ├── LambdaExpressionCollector.java │ │ │ │ ├── LambdaExpressionConverter.java │ │ │ │ ├── StaticInterfaceMethodConverter.java │ │ │ │ ├── StreamSupportConverter.java │ │ │ │ └── StringConcatenationConverter.java │ │ │ ├── classfile/ │ │ │ │ ├── ClassMemberPair.java │ │ │ │ ├── pass/ │ │ │ │ │ └── PrimitiveArrayConstantIntroducer.java │ │ │ │ └── visitor/ │ │ │ │ └── InjectedClassFilter.java │ │ │ ├── configuration/ │ │ │ │ ├── ConfigurationLogger.java │ │ │ │ ├── ConfigurationLoggingAdder.java │ │ │ │ ├── ConfigurationLoggingInstructionSequenceConstants.java │ │ │ │ ├── ConfigurationLoggingInstructionSequenceReplacer.java │ │ │ │ ├── ConfigurationLoggingInstructionSequencesReplacer.java │ │ │ │ └── InitialStateInfo.java │ │ │ ├── evaluation/ │ │ │ │ └── AssumeClassSpecificationVisitorFactory.java │ │ │ ├── fixer/ │ │ │ │ └── kotlin/ │ │ │ │ ├── KotlinAnnotationCounter.java │ │ │ │ └── KotlinAnnotationFlagFixer.java │ │ │ ├── io/ │ │ │ │ ├── ClassMapDataEntryReplacer.java │ │ │ │ ├── ExtraDataEntryNameMap.java │ │ │ │ ├── ExtraDataEntryReader.java │ │ │ │ ├── KotlinModuleRewriter.java │ │ │ │ └── UniqueDataEntryWriter.java │ │ │ ├── logging/ │ │ │ │ └── Logging.java │ │ │ ├── mark/ │ │ │ │ └── Marker.java │ │ │ ├── obfuscate/ │ │ │ │ ├── AttributeShrinker.java │ │ │ │ ├── AttributeUsageMarker.java │ │ │ │ ├── ClassNameAdapterFunction.java │ │ │ │ ├── ClassObfuscator.java │ │ │ │ ├── ClassRenamer.java │ │ │ │ ├── DictionaryNameFactory.java │ │ │ │ ├── MapCleaner.java │ │ │ │ ├── MappingKeeper.java │ │ │ │ ├── MappingPrinter.java │ │ │ │ ├── MappingProcessor.java │ │ │ │ ├── MappingReader.java │ │ │ │ ├── MemberNameCleaner.java │ │ │ │ ├── MemberNameCollector.java │ │ │ │ ├── MemberNameConflictFixer.java │ │ │ │ ├── MemberObfuscator.java │ │ │ │ ├── MemberSpecialNameFilter.java │ │ │ │ ├── MultiMappingProcessor.java │ │ │ │ ├── NameFactory.java │ │ │ │ ├── NameFactoryResetter.java │ │ │ │ ├── NameMarker.java │ │ │ │ ├── NameObfuscationReferenceFixer.java │ │ │ │ ├── NewMemberNameFilter.java │ │ │ │ ├── NumericNameFactory.java │ │ │ │ ├── ObfuscationPreparation.java │ │ │ │ ├── Obfuscator.java │ │ │ │ ├── OriginalClassNameFilter.java │ │ │ │ ├── ParameterNameMarker.java │ │ │ │ ├── PrefixingNameFactory.java │ │ │ │ ├── RenamedFlagSetter.java │ │ │ │ ├── ResourceFileNameAdapter.java │ │ │ │ ├── ResourceFileNameObfuscator.java │ │ │ │ ├── ResourceJavaReferenceFixer.java │ │ │ │ ├── SimpleNameFactory.java │ │ │ │ ├── SourceFileRenamer.java │ │ │ │ ├── SpecialNameFactory.java │ │ │ │ ├── UniqueMemberNameFactory.java │ │ │ │ ├── kotlin/ │ │ │ │ │ ├── KotlinAliasNameObfuscator.java │ │ │ │ │ ├── KotlinAliasReferenceFixer.java │ │ │ │ │ ├── KotlinCallableReferenceFixer.java │ │ │ │ │ ├── KotlinCompanionEqualizer.java │ │ │ │ │ ├── KotlinDefaultImplsMethodNameEqualizer.java │ │ │ │ │ ├── KotlinDefaultMethodNameEqualizer.java │ │ │ │ │ ├── KotlinIntrinsicsReplacementSequences.java │ │ │ │ │ ├── KotlinModuleFixer.java │ │ │ │ │ ├── KotlinModuleNameObfuscator.java │ │ │ │ │ ├── KotlinMultiFileFacadeFixer.java │ │ │ │ │ ├── KotlinObjectFixer.java │ │ │ │ │ ├── KotlinPropertyNameObfuscator.java │ │ │ │ │ ├── KotlinPropertyRenamer.java │ │ │ │ │ ├── KotlinSourceDebugExtensionAttributeObfuscator.java │ │ │ │ │ ├── KotlinSyntheticClassFixer.java │ │ │ │ │ ├── KotlinSyntheticToStringObfuscator.java │ │ │ │ │ ├── KotlinUnsupportedExceptionReplacementSequences.java │ │ │ │ │ ├── KotlinValueParameterNameShrinker.java │ │ │ │ │ └── KotlinValueParameterUsageMarker.java │ │ │ │ ├── obfuscate/ │ │ │ │ │ └── package.html │ │ │ │ └── util/ │ │ │ │ ├── InstructionSequenceObfuscator.java │ │ │ │ └── ReplacementSequences.java │ │ │ ├── optimize/ │ │ │ │ ├── BootstrapMethodArgumentShrinker.java │ │ │ │ ├── CalledMemberVisitor.java │ │ │ │ ├── ChangedCodePrinter.java │ │ │ │ ├── ConstantMemberFilter.java │ │ │ │ ├── ConstantParameterFilter.java │ │ │ │ ├── DuplicateInitializerFixer.java │ │ │ │ ├── DuplicateInitializerInvocationFixer.java │ │ │ │ ├── InfluenceFixpointVisitor.java │ │ │ │ ├── KeepMarker.java │ │ │ │ ├── KeptClassFilter.java │ │ │ │ ├── KeptMemberFilter.java │ │ │ │ ├── LineNumberTrimmer.java │ │ │ │ ├── MemberDescriptorSpecializer.java │ │ │ │ ├── MemberReferenceGeneralizer.java │ │ │ │ ├── MethodDescriptorShrinker.java │ │ │ │ ├── MethodStaticizer.java │ │ │ │ ├── OptimizationInfoClassFilter.java │ │ │ │ ├── OptimizationInfoMemberFilter.java │ │ │ │ ├── Optimizer.java │ │ │ │ ├── ParameterShrinker.java │ │ │ │ ├── ReverseDependencyCalculator.java │ │ │ │ ├── ReverseDependencyStore.java │ │ │ │ ├── SideEffectVisitorMarkerFactory.java │ │ │ │ ├── TailRecursionSimplifier.java │ │ │ │ ├── TimedClassPoolVisitor.java │ │ │ │ ├── WriteOnlyFieldFilter.java │ │ │ │ ├── evaluation/ │ │ │ │ │ ├── EvaluationShrinker.java │ │ │ │ │ ├── EvaluationSimplifier.java │ │ │ │ │ ├── InstructionUsageMarker.java │ │ │ │ │ ├── LoadingInvocationUnit.java │ │ │ │ │ ├── ParameterTracingInvocationUnit.java │ │ │ │ │ ├── SimpleEnumArrayPropagator.java │ │ │ │ │ ├── SimpleEnumClassChecker.java │ │ │ │ │ ├── SimpleEnumClassSimplifier.java │ │ │ │ │ ├── SimpleEnumDescriptorSimplifier.java │ │ │ │ │ ├── SimpleEnumUseChecker.java │ │ │ │ │ ├── SimpleEnumUseSimplifier.java │ │ │ │ │ ├── StoringInvocationUnit.java │ │ │ │ │ └── VariableOptimizer.java │ │ │ │ ├── gson/ │ │ │ │ │ ├── DuplicateJsonFieldNameChecker.java │ │ │ │ │ ├── FieldSignatureCollector.java │ │ │ │ │ ├── GsonAnnotationCleaner.java │ │ │ │ │ ├── GsonBuilderInvocationFinder.java │ │ │ │ │ ├── GsonClassConstants.java │ │ │ │ │ ├── GsonConstructorPatcher.java │ │ │ │ │ ├── GsonContext.java │ │ │ │ │ ├── GsonDeserializationInvocationFinder.java │ │ │ │ │ ├── GsonDeserializationOptimizer.java │ │ │ │ │ ├── GsonDomainClassFinder.java │ │ │ │ │ ├── GsonInstrumentationAdder.java │ │ │ │ │ ├── GsonOptimizer.java │ │ │ │ │ ├── GsonRuntimeSettings.java │ │ │ │ │ ├── GsonSerializationInvocationFinder.java │ │ │ │ │ ├── GsonSerializationOptimizer.java │ │ │ │ │ ├── InlineDeserializer.java │ │ │ │ │ ├── InlineDeserializers.java │ │ │ │ │ ├── InlineSerializer.java │ │ │ │ │ ├── InlineSerializers.java │ │ │ │ │ ├── LocalOrAnonymousClassChecker.java │ │ │ │ │ ├── MarkedAnnotationDeleter.java │ │ │ │ │ ├── OptimizedClassConstants.java │ │ │ │ │ ├── OptimizedJsonFieldCollector.java │ │ │ │ │ ├── OptimizedJsonFieldVisitor.java │ │ │ │ │ ├── OptimizedJsonInfo.java │ │ │ │ │ ├── OptimizedJsonReaderImplInitializer.java │ │ │ │ │ ├── OptimizedJsonWriterImplInitializer.java │ │ │ │ │ ├── OptimizedTypeAdapterAdder.java │ │ │ │ │ ├── OptimizedTypeAdapterFactoryInitializer.java │ │ │ │ │ ├── OptimizedTypeAdapterInitializer.java │ │ │ │ │ ├── TypeArgumentFinder.java │ │ │ │ │ ├── TypeParameterClassChecker.java │ │ │ │ │ ├── TypeTokenClassBuilder.java │ │ │ │ │ ├── _GsonUtil.java │ │ │ │ │ ├── _OptimizedJsonReader.java │ │ │ │ │ ├── _OptimizedJsonReaderImpl.java │ │ │ │ │ ├── _OptimizedJsonWriter.java │ │ │ │ │ ├── _OptimizedJsonWriterImpl.java │ │ │ │ │ ├── _OptimizedTypeAdapter.java │ │ │ │ │ ├── _OptimizedTypeAdapterFactory.java │ │ │ │ │ ├── _OptimizedTypeAdapterImpl.java │ │ │ │ │ └── package.html │ │ │ │ ├── info/ │ │ │ │ │ ├── AccessMethodMarker.java │ │ │ │ │ ├── BackwardBranchMarker.java │ │ │ │ │ ├── CatchExceptionMarker.java │ │ │ │ │ ├── CaughtClassFilter.java │ │ │ │ │ ├── CaughtClassMarker.java │ │ │ │ │ ├── ClassOptimizationInfo.java │ │ │ │ │ ├── CodeAttributeOptimizationInfo.java │ │ │ │ │ ├── ContainsConstructorsMarker.java │ │ │ │ │ ├── DotClassFilter.java │ │ │ │ │ ├── DotClassMarker.java │ │ │ │ │ ├── DynamicInvocationMarker.java │ │ │ │ │ ├── EscapingClassFilter.java │ │ │ │ │ ├── EscapingClassMarker.java │ │ │ │ │ ├── ExceptionInstructionChecker.java │ │ │ │ │ ├── FieldOptimizationInfo.java │ │ │ │ │ ├── FinalFieldAssignmentMarker.java │ │ │ │ │ ├── InstanceofClassFilter.java │ │ │ │ │ ├── InstanceofClassMarker.java │ │ │ │ │ ├── InstantiationClassFilter.java │ │ │ │ │ ├── InstantiationClassMarker.java │ │ │ │ │ ├── MethodInvocationMarker.java │ │ │ │ │ ├── MethodOptimizationInfo.java │ │ │ │ │ ├── MutableBoolean.java │ │ │ │ │ ├── NoEscapingParametersMethodMarker.java │ │ │ │ │ ├── NoExternalReturnValuesMethodMarker.java │ │ │ │ │ ├── NoExternalSideEffectMethodMarker.java │ │ │ │ │ ├── NoSideEffectClassMarker.java │ │ │ │ │ ├── NoSideEffectMethodMarker.java │ │ │ │ │ ├── NonEmptyStackReturnMarker.java │ │ │ │ │ ├── NonPrivateMemberMarker.java │ │ │ │ │ ├── OptimizationCodeAttributeFilter.java │ │ │ │ │ ├── PackageVisibleMemberContainingClassMarker.java │ │ │ │ │ ├── PackageVisibleMemberInvokingClassMarker.java │ │ │ │ │ ├── ParameterEscapeMarker.java │ │ │ │ │ ├── ParameterEscapedMarker.java │ │ │ │ │ ├── ParameterUsageMarker.java │ │ │ │ │ ├── ProgramClassOptimizationInfo.java │ │ │ │ │ ├── ProgramClassOptimizationInfoSetter.java │ │ │ │ │ ├── ProgramFieldOptimizationInfo.java │ │ │ │ │ ├── ProgramMemberOptimizationInfoSetter.java │ │ │ │ │ ├── ProgramMethodOptimizationInfo.java │ │ │ │ │ ├── ReadWriteFieldMarker.java │ │ │ │ │ ├── ReferenceEscapeChecker.java │ │ │ │ │ ├── RepeatedClassPoolVisitor.java │ │ │ │ │ ├── SideEffectClassChecker.java │ │ │ │ │ ├── SideEffectClassFilter.java │ │ │ │ │ ├── SideEffectClassMarker.java │ │ │ │ │ ├── SideEffectInstructionChecker.java │ │ │ │ │ ├── SideEffectMethodFilter.java │ │ │ │ │ ├── SideEffectMethodMarker.java │ │ │ │ │ ├── SimpleEnumFilter.java │ │ │ │ │ ├── SimpleEnumMarker.java │ │ │ │ │ ├── SuperInvocationMarker.java │ │ │ │ │ ├── SynchronizedBlockMethodMarker.java │ │ │ │ │ ├── UnusedParameterMethodFilter.java │ │ │ │ │ ├── UnusedParameterOptimizationInfoUpdater.java │ │ │ │ │ ├── UsedParameterFilter.java │ │ │ │ │ ├── VariableUsageMarker.java │ │ │ │ │ └── WrapperClassMarker.java │ │ │ │ ├── kotlin/ │ │ │ │ │ └── KotlinContextReceiverUsageMarker.java │ │ │ │ └── peephole/ │ │ │ │ ├── ClassFinalizer.java │ │ │ │ ├── ClassMerger.java │ │ │ │ ├── GotoCommonCodeReplacer.java │ │ │ │ ├── GotoGotoReplacer.java │ │ │ │ ├── GotoReturnReplacer.java │ │ │ │ ├── HorizontalClassMerger.java │ │ │ │ ├── InstructionSequenceConstants.java │ │ │ │ ├── LineNumberLinearizer.java │ │ │ │ ├── MemberPrivatizer.java │ │ │ │ ├── MethodFinalizer.java │ │ │ │ ├── MethodInliner.java │ │ │ │ ├── NoConstructorReferenceReplacer.java │ │ │ │ ├── NopRemover.java │ │ │ │ ├── ReachableCodeMarker.java │ │ │ │ ├── RetargetedClassFilter.java │ │ │ │ ├── RetargetedInnerClassAttributeRemover.java │ │ │ │ ├── ShortMethodInliner.java │ │ │ │ ├── SingleInvocationMethodInliner.java │ │ │ │ ├── TargetClassChanger.java │ │ │ │ ├── UnreachableCodeRemover.java │ │ │ │ ├── UnreachableExceptionRemover.java │ │ │ │ ├── VariableShrinker.java │ │ │ │ ├── VerticalClassMerger.java │ │ │ │ ├── WrapperClassMerger.java │ │ │ │ └── WrapperClassUseSimplifier.java │ │ │ ├── pass/ │ │ │ │ ├── Pass.java │ │ │ │ └── PassRunner.java │ │ │ ├── preverify/ │ │ │ │ ├── PreverificationClearer.java │ │ │ │ ├── Preverifier.java │ │ │ │ └── SubroutineInliner.java │ │ │ ├── shrink/ │ │ │ │ ├── AnnotationUsageMarker.java │ │ │ │ ├── ClassShrinker.java │ │ │ │ ├── ClassUsageMarker.java │ │ │ │ ├── InnerUsageMarker.java │ │ │ │ ├── InterfaceUsageMarker.java │ │ │ │ ├── KotlinModuleShrinker.java │ │ │ │ ├── KotlinModuleUsageMarker.java │ │ │ │ ├── KotlinShrinker.java │ │ │ │ ├── LocalVariableTypeUsageMarker.java │ │ │ │ ├── NestUsageMarker.java │ │ │ │ ├── RecordComponentUsageMarker.java │ │ │ │ ├── ShortestClassUsageMarker.java │ │ │ │ ├── ShortestUsageMark.java │ │ │ │ ├── ShortestUsageMarker.java │ │ │ │ ├── ShortestUsagePrinter.java │ │ │ │ ├── Shrinker.java │ │ │ │ ├── SimpleUsageMarker.java │ │ │ │ ├── UsageMarker.java │ │ │ │ ├── UsagePrinter.java │ │ │ │ ├── UsedClassFilter.java │ │ │ │ ├── UsedMemberFilter.java │ │ │ │ └── shrink/ │ │ │ │ └── package.html │ │ │ ├── strip/ │ │ │ │ └── KotlinAnnotationStripper.java │ │ │ └── util/ │ │ │ ├── Benchmark.java │ │ │ ├── HashUtil.java │ │ │ ├── PrintWriterUtil.java │ │ │ ├── TimeUtil.java │ │ │ ├── TransformedStringMatcher.java │ │ │ └── kotlin/ │ │ │ ├── KotlinUnsupportedVersionChecker.java │ │ │ └── asserter/ │ │ │ ├── KotlinMetadataVerifier.java │ │ │ └── Reporter.java │ │ └── resources/ │ │ └── log4j2.xml │ └── test/ │ └── kotlin/ │ ├── proguard/ │ │ ├── AfterInitConfigurationVerifierTest.kt │ │ ├── ClassMergerTest.kt │ │ ├── ClassSpecificationVisitorFactoryTest.kt │ │ ├── ConfigurationParserTest.kt │ │ ├── Java19RecordPatternTest.kt │ │ ├── Java20RecordPatternTest.kt │ │ ├── LoggingTest.kt │ │ ├── MarkerTest.kt │ │ ├── obfuscate/ │ │ │ ├── SimpleNameFactoryTest.kt │ │ │ └── kotlin/ │ │ │ ├── KotlinCallableReferenceFixerTest.kt │ │ │ ├── KotlinIntrinsicsReplacementSequencesTest.kt │ │ │ └── KotlinValueParameterNameShrinkerTest.kt │ │ ├── optimize/ │ │ │ ├── KotlinContextReceiverParameterShrinkingTest.kt │ │ │ ├── MemberDescriptorSpecializerTest.kt │ │ │ ├── SimpleEnumClassCheckerTest.kt │ │ │ ├── gson/ │ │ │ │ ├── MarkedAnnotationDeleterTest.kt │ │ │ │ └── TypeArgumentFinderTest.kt │ │ │ ├── info/ │ │ │ │ └── InstantiationClassMarkerTest.kt │ │ │ └── peephole/ │ │ │ └── MethodInlinerTest.kt │ │ ├── shrink/ │ │ │ └── ClassUsageMarkerTest.kt │ │ └── util/ │ │ └── ProguardAssemblerTest.kt │ └── testutils/ │ ├── ConfigurationUtil.kt │ ├── JavaUtil.kt │ ├── LogUtils.kt │ ├── ProcessingFlagUtil.kt │ └── TestConfig.kt ├── bin/ │ ├── dprotect.sh │ ├── proguard.bat │ ├── proguard.sh │ ├── proguardgui.bat │ ├── proguardgui.sh │ ├── retrace.bat │ └── retrace.sh ├── build.gradle ├── docs/ │ ├── md/ │ │ ├── css/ │ │ │ ├── admonition.css │ │ │ └── extra.css │ │ ├── downloads.md │ │ ├── index.md │ │ ├── manual/ │ │ │ ├── FAQ.md │ │ │ ├── building.md │ │ │ ├── configuration/ │ │ │ │ ├── attributes.md │ │ │ │ ├── examples.md │ │ │ │ ├── optimizations.md │ │ │ │ └── usage.md │ │ │ ├── feedback.md │ │ │ ├── home.md │ │ │ ├── languages/ │ │ │ │ ├── java.md │ │ │ │ └── kotlin.md │ │ │ ├── license/ │ │ │ │ ├── gpl.md │ │ │ │ ├── gplexception.md │ │ │ │ └── license.md │ │ │ ├── quickstart.md │ │ │ ├── refcard.md │ │ │ ├── releasenotes.md │ │ │ ├── setup/ │ │ │ │ ├── ant.md │ │ │ │ ├── gradle.md │ │ │ │ ├── gradleplugin.md │ │ │ │ └── standalone.md │ │ │ ├── tools/ │ │ │ │ ├── appsweep.md │ │ │ │ ├── playground.md │ │ │ │ └── retrace.md │ │ │ └── troubleshooting/ │ │ │ ├── limitations.md │ │ │ └── troubleshooting.md │ │ └── results.md │ ├── mkdocs.yml │ └── notices/ │ └── Gson ├── dprotect/ │ └── build.gradle ├── examples/ │ ├── android-agp3-agp4/ │ │ ├── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── proguard-project.txt │ │ ├── res/ │ │ │ └── values/ │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── settings.gradle │ │ └── src/ │ │ └── com/ │ │ └── example/ │ │ └── HelloWorldActivity.java │ ├── android-plugin/ │ │ ├── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── proguard-project.txt │ │ ├── res/ │ │ │ └── values/ │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── settings.gradle │ │ └── src/ │ │ └── com/ │ │ └── example/ │ │ └── HelloWorldActivity.java │ ├── annotations/ │ │ ├── examples/ │ │ │ ├── Applet.java │ │ │ ├── Application.java │ │ │ ├── Bean.java │ │ │ └── NativeCallBack.java │ │ └── examples.pro │ ├── ant/ │ │ ├── applets.xml │ │ ├── applications1.xml │ │ ├── applications2.xml │ │ ├── applications3.xml │ │ ├── library.xml │ │ ├── midlets.xml │ │ ├── proguard.xml │ │ └── servlets.xml │ ├── application/ │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── proguard.pro │ │ ├── settings.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── App.java │ ├── application-kotlin/ │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── proguard.pro │ │ ├── settings.gradle │ │ └── src/ │ │ └── main/ │ │ └── kotlin/ │ │ └── App.kt │ ├── dictionaries/ │ │ ├── compact.txt │ │ ├── keywords.txt │ │ ├── shakespeare.txt │ │ └── windows.txt │ ├── gradle/ │ │ ├── android.gradle │ │ ├── applets.gradle │ │ ├── applications.gradle │ │ ├── library.gradle │ │ ├── midlets.gradle │ │ ├── proguard.gradle │ │ ├── proguardgui.gradle │ │ ├── retrace.gradle │ │ ├── scala.gradle │ │ ├── servlets.gradle │ │ └── settings.gradle │ ├── gradle-kotlin-dsl/ │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle.kts │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── gradlekotlindsl/ │ │ │ └── App.java │ │ └── test/ │ │ └── java/ │ │ └── gradlekotlindsl/ │ │ └── AppTest.java │ ├── spring-boot/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── demo/ │ │ │ ├── DemoApplication.java │ │ │ ├── TestComponent.java │ │ │ └── sub/ │ │ │ └── TestComponent2.java │ │ └── resources/ │ │ └── application.properties │ └── standalone/ │ ├── android.pro │ ├── applets.pro │ ├── applications.pro │ ├── library.pro │ ├── midlets.pro │ ├── proguard.pro │ ├── proguardgui.pro │ ├── retrace.pro │ ├── scala.pro │ └── servlets.pro ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle-plugin/ │ ├── .gitignore │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── proguard/ │ │ │ └── gradle/ │ │ │ └── ProGuardTask.java │ │ ├── kotlin/ │ │ │ └── proguard/ │ │ │ └── gradle/ │ │ │ └── plugin/ │ │ │ ├── ProGuardPlugin.kt │ │ │ └── android/ │ │ │ ├── AndroidPlugin.kt │ │ │ ├── ProGuardTransform.kt │ │ │ ├── dsl/ │ │ │ │ ├── ProGuardAndroidExtension.kt │ │ │ │ ├── ProGuardConfiguration.kt │ │ │ │ └── VariantConfiguration.kt │ │ │ ├── tasks/ │ │ │ │ ├── CollectConsumerRulesTask.kt │ │ │ │ └── PrepareProguardConfigDirectoryTask.kt │ │ │ └── transforms/ │ │ │ ├── AndroidConsumerRulesTransform.kt │ │ │ └── ArchiveConsumerRulesTransform.kt │ │ └── resources/ │ │ └── lib/ │ │ ├── proguard-android-common.txt │ │ ├── proguard-android-debug.txt │ │ ├── proguard-android-optimize.txt │ │ └── proguard-android.txt │ └── test/ │ └── kotlin/ │ ├── proguard/ │ │ └── gradle/ │ │ ├── AgpVersionParserTest.kt │ │ ├── ConsumerRulesCollectionTest.kt │ │ ├── ConsumerRulesFilterTest.kt │ │ ├── GradlePluginIntegrationTest.kt │ │ ├── ProGuardPluginLegacyAGPIntegrationTest.kt │ │ ├── ProGuardPluginTest.kt │ │ ├── ProguardCacheRelocateabilityIntegrationTest.kt │ │ └── plugin/ │ │ └── android/ │ │ └── dsl/ │ │ ├── AaptRulesTest.kt │ │ ├── ConfigurationOrderTest.kt │ │ ├── ConfigurationTest.kt │ │ ├── DefaultConfigurationTest.kt │ │ └── FlavorsConfigurationTest.kt │ └── testutils/ │ ├── AndroidProjectBuilder.kt │ ├── GradleRunnerUtil.kt │ └── TestPluginClasspath.kt ├── gradle.properties ├── gradlew ├── gradlew.bat ├── gui/ │ ├── build.gradle │ └── src/ │ └── proguard/ │ └── gui/ │ ├── ClassPathPanel.java │ ├── ClassSpecificationDialog.java │ ├── ClassSpecificationsPanel.java │ ├── ExtensionFileFilter.java │ ├── FilterBuilder.java │ ├── FilterDialog.java │ ├── GUIResources.java │ ├── GUIResources.properties │ ├── JavaUtil.java │ ├── KeepSpecificationsPanel.java │ ├── ListPanel.java │ ├── MemberSpecificationDialog.java │ ├── MemberSpecificationsPanel.java │ ├── MessageDialogRunnable.java │ ├── OptimizationsDialog.java │ ├── ProGuardGUI.java │ ├── ProGuardRunnable.java │ ├── ReTraceRunnable.java │ ├── SwingUtil.java │ ├── TabbedPane.java │ ├── TextAreaOutputStream.java │ ├── TextAreaWriter.java │ ├── boilerplate.pro │ ├── default.pro │ ├── package.html │ └── splash/ │ ├── BufferedSprite.java │ ├── CircleSprite.java │ ├── ClipSprite.java │ ├── ColorSprite.java │ ├── CompositeSprite.java │ ├── ConstantColor.java │ ├── ConstantDouble.java │ ├── ConstantFont.java │ ├── ConstantInt.java │ ├── ConstantString.java │ ├── ConstantTiming.java │ ├── FontSprite.java │ ├── ImageSprite.java │ ├── LinearColor.java │ ├── LinearDouble.java │ ├── LinearInt.java │ ├── LinearTiming.java │ ├── OverrideGraphics2D.java │ ├── RectangleSprite.java │ ├── SawToothTiming.java │ ├── ShadowedSprite.java │ ├── SineTiming.java │ ├── SmoothTiming.java │ ├── SplashPanel.java │ ├── Sprite.java │ ├── TextSprite.java │ ├── TimeSwitchSprite.java │ ├── Timing.java │ ├── TypeWriterString.java │ ├── VariableColor.java │ ├── VariableDouble.java │ ├── VariableFont.java │ ├── VariableInt.java │ ├── VariableSizeFont.java │ ├── VariableString.java │ └── package.html ├── proguard-app/ │ └── build.gradle ├── retrace/ │ ├── build.gradle │ └── src/ │ └── proguard/ │ └── retrace/ │ ├── FrameInfo.java │ ├── FramePattern.java │ ├── FrameRemapper.java │ ├── ReTrace.java │ └── package.html ├── scripts/ │ ├── docker/ │ │ ├── gh_action.sh │ │ └── publish_gradle_plugin.sh │ └── fetch_dprotect_core.py └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*.java] indent_style = space indent_size = 4 ================================================ FILE: .gitattributes ================================================ gradlew -text gradlew.bat -text ================================================ FILE: .github/scripts/s3-deploy.py ================================================ #!/usr/bin/env python3 import sys import os import logging import pathlib from mako.template import Template from enum import Enum, auto import tempfile import boto3 from botocore.exceptions import ClientError class CI(Enum): UNKNOWN = auto() GITHUB_ACTIONS = auto() def pretty_ci_name(ci): return str(ci).split(".")[-1].replace("_", "-").lower() def is_pr(ci): if ci == CI.GITHUB_ACTIONS: cond1 = os.getenv("GITHUB_HEAD_REF", "") != "" cond2 = not (os.getenv("GITHUB_REPOSITORY", "").startswith("open-obfuscator/") or os.getenv("GITHUB_REPOSITORY", "").startswith("romainthomas/")) return cond1 or cond2 return True def get_branch(ci): if ci == CI.GITHUB_ACTIONS: return os.getenv("GITHUB_REF").replace("refs/heads/", "") return None def get_ci_workdir(ci): if ci == CI.GITHUB_ACTIONS: return os.getenv("GITHUB_WORKSPACE") logger.critical("Unsupported CI to resolve working directory") sys.exit(1) def get_tag(ci): if ci == CI.GITHUB_ACTIONS: ref = os.getenv("GITHUB_REF", "") logger.info("Github Action tag: {}".format(ref)) if ref.startswith("refs/tags/"): return ref.replace("refs/tags/", "") return "" logger.critical("Unsupported CI to resolve working directory") sys.exit(1) LOG_LEVEL = logging.INFO logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout)) logging.getLogger().setLevel(LOG_LEVEL) logger = logging.getLogger(__name__) CURRENT_CI = CI.GITHUB_ACTIONS CI_PRETTY_NAME = pretty_ci_name(CURRENT_CI) logger.info("CI: %s", CI_PRETTY_NAME) ALLOWED_BRANCHES = {"main", "deploy", "devel"} BRANCH_NAME = get_branch(CURRENT_CI) TAG_NAME = get_tag(CURRENT_CI) IS_TAGGED = TAG_NAME is not None and len(TAG_NAME) > 0 logger.info("Branch: %s", BRANCH_NAME) logger.info("Tag: %s", TAG_NAME) if BRANCH_NAME.startswith("release-"): logger.info("Branch release") elif BRANCH_NAME not in ALLOWED_BRANCHES and not IS_TAGGED: logger.info("Skip deployment for branch '%s'", BRANCH_NAME) sys.exit(0) if is_pr(CURRENT_CI): logger.info("Skip pull request") sys.exit(0) CURRENTDIR = pathlib.Path(__file__).resolve().parent REPODIR = CURRENTDIR / ".." / ".." # According to Scaleway S3 documentation, the endpoint # should starts with ''.s3..scw.cloud # Nevertheless boto3 uses /{Bucket} endpoints suffix # which create issues (see: https://stackoverflow.com/a/70383653) DPROTECT_S3_REGION = "fr-par" DPROTECT_S3_ENDPOINT = "https://s3.{region}.scw.cloud".format(region=DPROTECT_S3_REGION) DPROTECT_S3_BUCKET = "obfuscator" DPROTECT_S3_KEY = os.getenv("DPROTECT_S3_KEY", None) DPROTECT_S3_SECRET = os.getenv("DPROTECT_S3_SECRET", None) if DPROTECT_S3_KEY is None or len(DPROTECT_S3_KEY) == 0: logger.error("OPEN_OBFUSCATOR_S3_KEY is not set!") sys.exit(1) if DPROTECT_S3_SECRET is None or len(DPROTECT_S3_SECRET) == 0: logger.error("OPEN_OBFUSCATOR_S3_SECRET is not set!") sys.exit(1) CI_CWD = pathlib.Path(get_ci_workdir(CURRENT_CI)) if CI_CWD is None: logger.error("Can't resolve CI working dir") sys.exit(1) DIST_DIR = REPODIR / "dist" logger.info("Working directory: %s", CI_CWD) INDEX_TEMPLATE = r""" Packages for dProtect

Packages for dProtect

% for path, filename in files: ${filename}
% endfor """ SKIP_LIST = ["index.html"] s3 = boto3.resource( 's3', region_name=DPROTECT_S3_REGION, use_ssl=True, endpoint_url=DPROTECT_S3_ENDPOINT, aws_access_key_id=DPROTECT_S3_KEY, aws_secret_access_key=DPROTECT_S3_SECRET ) def push(file: str, dir_name: str): zipfile = pathlib.Path(file) dst = f"{dir_name}/dprotect/{zipfile.name}" logger.info("Uploading %s to %s", file, dst) try: obj = s3.Object(DPROTECT_S3_BUCKET, dst) obj.put(Body=zipfile.read_bytes()) return 0 except ClientError as e: logger.error("S3 push failed: %s", e) return 1 def filename(object): return pathlib.Path(object.key).name def generate_index(dir_name: str): files = s3.Bucket(DPROTECT_S3_BUCKET).objects.filter(Prefix=f'{dir_name}/dprotect') tmpl_info = [(object.key, filename(object)) for object in files if filename(object) not in SKIP_LIST] html = Template(INDEX_TEMPLATE).render(files=tmpl_info) return html dir_name = "latest" if BRANCH_NAME != "main": dir_name = "{}".format(BRANCH_NAME.replace("/", "-").replace("_", "-")) if BRANCH_NAME.startswith("release-"): _, dir_name = BRANCH_NAME.split("release-") if IS_TAGGED: dir_name = str(TAG_NAME) logger.info("Destination directory: %s", dir_name) for file in DIST_DIR.glob("*.zip"): logger.info("[ZIP ] Uploading '%s'", file.as_posix()) push(file.as_posix(), dir_name) nightly_index = generate_index(dir_name) with tempfile.TemporaryDirectory() as tmp: tmp = pathlib.Path(tmp) index = (tmp / "index.html") index.write_text(nightly_index) push(index.as_posix(), dir_name) logger.info("Done!") ================================================ FILE: .github/workflows/continuous_integration.yml ================================================ name: Continuous Integration on: pull_request: branches: - skip - skip push: branches: - skip - skip jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v2 with: path: proguard-main - uses: actions/setup-java@v1 with: java-version: 8 - uses: eskatos/gradle-command-action@v1 with: build-root-directory: proguard-main/ wrapper-directory: proguard-main/ arguments: test :base:testAllJavaVersions :base:jacocoTestReport jar --info - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() # always run even if the previous step fails with: report_paths: '**/build/test-results/test/TEST-*.xml' ================================================ FILE: .github/workflows/main.yml ================================================ name: 'dProtect Main' on: [push, workflow_dispatch] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Build dProtect-core & dProtect shell: bash run: | mkdir -p $GITHUB_WORKSPACE/dist docker run --rm \ -v $GITHUB_WORKSPACE:/dprotect \ openobfuscator/dprotect-build:latest bash /dprotect/scripts/docker/gh_action.sh - name: Deploy env: DPROTECT_S3_KEY: ${{ secrets.DPROTECT_S3_KEY }} DPROTECT_S3_SECRET: ${{ secrets.DPROTECT_S3_SECRET }} shell: bash run: | docker run \ -v $GITHUB_WORKSPACE:/dprotect \ -e GITHUB_ACTIONS="true" \ -e GITHUB_WORKSPACE=$GITHUB_WORKSPACE \ -e GITHUB_REF=$GITHUB_REF \ -e GITHUB_REPOSITORY=$GITHUB_REPOSITORY \ -e DPROTECT_S3_KEY=$DPROTECT_S3_KEY \ -e DPROTECT_S3_SECRET=$DPROTECT_S3_SECRET \ --rm \ openobfuscator/deployment python3 /dprotect/.github/scripts/s3-deploy.py ================================================ FILE: .gitignore ================================================ .gradle .hg/** .hgtags .idea build local.properties /lib/ docs/html ================================================ FILE: .hgignore ================================================ syntax: glob .git/* ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to ProGuard Thank you for contributing! - You can report issues on our [issue tracker](issues) on GitHub. Please be concise and complete. If we can't reproduce an issue, we most likely can't fix it. - You can also clone the [source code](.) yourself and create [pull requests](pulls) on GitHub. These are a few guidelines for our code and documentation. ## Basic principle Above all, code should be consistent. Study, respect, and follow the style of the existing code. ## Code design The code generally defines many short classes, as the building blocks for classes that contain the real logic. This is sometimes taken to the extreme: even loops and conditional statements can often be implemented as separate classes. Even though these classes are verbose and repetitive, the resulting main code becomes much more compact, flexible, and robust. ### Data classes Basic data classes define the data structures, with just a minimum number of operations: Java bytecode classes, Dex bytecode classes, XML data, native libraries, etc. They typically reflect their respective specifications literally. The data classes have one or more `accept` methods to let the visitor classes below operate on them. ### Visitors The code relies a lot on the visitor pattern. Visitor classes define the operations on the data: reading, writing, editing, transforming, etc. The visitor classes have one or more `visit` methods to operate on data classes of the same basic type. For example, a Java bytecode class contains a constant pool with constants of different types: integer constants, float constants, string constants, etc. The data classes IntegerConstant, FloatConstant, StringConstant, etc. all implement the basic type Constant. The visitor interface ConstantVisitor contains methods 'visitIntegerConstant', 'visitFloatConstant', 'visitStringConstant', etc. Implementations of this visitor interface can perform all kinds of operations on the constants. The reasoning behind this pattern is that the data classes are very stable, because they are directly based on the specifications (class files, dex files, binary XML files, ELF files, etc). The operations are more dynamic. They are changed and extended all the time. It is practically impossible to add all possible operations in the data classes, but it is easy to add another implementation of a visitor interface. Implementing an interface in practice helps a lot to think of all possible cases. The visitor pattern uses visitor interfaces to operate on the similar elements of a data structure. Each interface often has many implementations. A great disadvantage at this time is that visitor methods can invoke one another (directly or indirectly), but they can't communicate easily. Since the implementations can't add their own parameters or return values, they often have to rely on fields to pass values back and forth. This is more error-prone. Still, the advantages of the visitor pattern outweigh the disadvantages. We prefer (visitor) interfaces with many classes that implement them, so the implementations can be used as building blocks. ### Facade classes With many small visitor classes available as building blocks, the important code chains these visitors together. The main ProGuard packages contain a number of facade classes that construct and run these chains. Notably: - `ProGuard` is the main entry point, with a long linear sequence of operations that it delegates to the facade classes. - `InputReader` reads the code and resources. - `Initializer` initializes links between the code and resources, to traverse the data structures more easily. - `Marker` marks code and resources to be kept, encrypted, etc., based on the configuration. - `Backporter` backports code to older versions of Java. - `ConfigurationLoggingAdder` instruments code to give feedback about potentially missing configuration. - `Shrinker` removes unused code and resources. - `Optimizer` optimizes the code and resources. - `Obfuscator` obfuscates the code and resources. - `Preverifier` adds preverification metadata to code for Java 6 and higher. ### Data flow At a high level, the flow of data inside ProGuard is as follows: - Traverse the input data, to parse any useful data structures. - Process the data structures in a number of steps (mainly shrinking, string encryption, optimization, obfuscation, class encryption). - Traverse the input data again, this time to write to the output, by copying, transforming, replacing, or removing data entries. The transformations can be small (e.g. replacing class names in properties files) or large (e.g. replacing all classes and resources by obfuscated counterparts). Important interfaces for this flow: - `DataEntryReader` uses a push mechanism. It typically traverses data entries (files and nested zip entries) in the input and pushes them to data entry visitors. The visitors can access the actual data through input streams. - `DataEntryWriter` uses a pull mechanism. It can return output streams for data entries. The first traversal and parsing of the input data is based on implementations of DataEntryReader. The second traversal of the input data is based on the same implementations of DataEntryReader, with additional implementations that use implementations of DataEntryWriter to transform and write the output data. ### Dependency injection We heavily use **constructor-based dependency injection**, to create immutable objects. Notably the visitors are often like commands that are combined in an immutable structure, via constructors. The commands are then executed by applying the visitors to the classes or other data structures. ### Getters and setters We try to **avoid getters and setters** outside of pure data classes. Generally, getters and setters break data encapsulation -- "Tell, don't ask". In our architecture, visitors often delegate to a chain of other visitors, with the final visitor applying actual changes to the data structures. Visitors that change the data classes often access fields directly, since they conceptually have the same purpose as methods inside the data classes. ## Code style Over time, we've adopted a few guidelines: - Historically, ProGuard hasn't used generics, enums, iterable loops, variable arguments, or closures. More recent code can use the features of Java 8, but be consistent. - Prefer **arrays** over lists, if their sizes are relatively static. The resulting code is easier to read, without casts or generics. - Prefer **short classes** over long classes, although long classes that simply implement the many methods of their interfaces are okay. - Prefer **short methods**, although long methods with a linear flow are okay. - **Declare and initialize** fields and variables at the same time, when possible. - Make fields **final**, when possible: ``` private final CodeAttributeComposer composer = new CodeAttributeComposer(); ``` - We generally don't make local variables final, to avoid cluttering the code too much. - Maintain a **consistent order** for fields, methods (e.g.. getters and setters), case statements, etc. A good basic rule for new classes is that their contents should be in chronological order or respecting the natural hierarchical order of the data -- Which fields are accessed first? Which methods are accessed first? Which data comes first in the data structures? For classes that implement or resemble other classes, the contents should respect the same order. ## Documentation All classes must have main javadoc: /** * This {@link ClassVisitor} collects the names of the classes that it * visits in the given collection. * * @author Eric Lafortune */ Methods should have javadoc: /** * Returns the number of parameters of the given internal method descriptor. * @param internalMethodDescriptor the internal method descriptor, * e.g. "(ID)Z". * @param isStatic specifies whether the method is static, * e.g. false. * @return the number of parameters, * e.g. 3. */ In practice, fields rarely have javadoc (this is debatable...) Methods that implement interfaces generally don't have javadoc, since they simply implement the behavior specified by the interfaces. Instead, a group of methods that implements an interface is documented with: // Implementations for SomeInterface. Most code has comments for each block of statements. The idea is to make the code readable by just glancing over the comments (which works well if they are colored in an IDE). Comments are generally short sentences, starting with a capital and ending with a period: // This is a comment. For the manual (written as markdown), you should follow the excellent advice of the [Google Developer Documentation Style Guide](https://developers.google.com/style/). ## Code format We strive for code that is super-aligned. Ideally, the code should look like a set of tables, for variable declarations, parameter declarations, ternary operators, etc. - The basic indentation is 4 spaces (never tabs). - Curly braces are on separate lines. - The ideal maximum line length is 120 characters. Documentation and comments should stick to this limit. Code that has a nice table-like structure can exceed it. - Imports of multiple classes in the same package are specified with a wildcard '*'. - Try to preserve a logical, consistent order in fields, getters/setters, methods, variables. - Whenever overriding a method, add the @Override annotation. In practice, an IDE can help to obtain a clean indentation and structure, but it is often not sufficient. Be careful to never upset a cleanly formatted source file by automatically reformatting it. ### Naming Conventions - Names (class names, field names, etc) should be descriptive. - We generally don't abbreviate names, not even index variables: ``` for (int index = 0; index < count; index++) ``` ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ================================================ FILE: README.md ================================================



dProtect

dProtect is an extension of Proguard with enhanced code obfuscation features. While Proguard is known for obfuscating symbols (package names, class names, ...) and optimizing Java/Kotlin code, dProtect brings code obfuscation for Java and Kotlin on the top of Proguard: ```bash # Arithmetic Obfuscation -obfuscate-arithmetic,low class dprotect.examples.** -obfuscate-arithmetic,high,skipfloat class dprotect.internal.** # Strings Obfuscation -obfuscate-strings class dprotect.examples.** -obfuscate-strings class dprotect.internal.CheckPassword { public static java.lang.String getPassword(); public static java.lang.String getLogin(); private static java.lang.String KEY; } # Control-flow obfuscation -obfuscate-control-flow class dprotect.internal.api.** # Constants Obfuscation -obfuscate-constants class dprotect.internal.api.Enum { *; } ``` dProtect follows the same integration mechanism as Proguard such as it can be instantiated in an Android Gradle-based project as follows:
build.gradle
```gradle buildscript { repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/open-obfuscator/dProtect") credentials { username = "your-username" password = "the-token" } } } dependencies { classpath 're.obfuscator:dprotect-gradle:1.0.0' } } ``` **You need to generate a Github token to use `maven.pkg.github.com`**. Alternatively, you can use the `dprotect-gradle-1.0.0.jar` archive of the release's artifacts.
app/build.gradle
```gradle apply plugin: 're.obfuscator.dprotect' // ... dProtect { configurations { release { configuration 'dprotect-rules.pro' } } } ``` dProtect Pipeline You can also find out more information here: https://obfuscator.re/dprotect ### Contact You can reach out by email at this address: `ping@obfuscator.re` #### Maintainers - [Romain Thomas](https://www.romainthomas.fr): [@rh0main](https://twitter.com/rh0main) - `me@romainthomas.fr` #### Credits - [Guardsquare](https://www.guardsquare.com/): Proguard's Owner - [Eric Lafortune](https://www.lafortune.eu/): Proguard's Author - [James Hamilton](https://jameshamilton.eu/): Proguard's Core dev/maintainer ### License dProtect is released under the same License as Proguard: [GNU General Public License v2.0](https://github.com/Guardsquare/proguard/blob/master/LICENSE) ================================================ FILE: annotations/annotations.pro ================================================ # # This ProGuard configuration file specifies how annotations can be used # to configure the processing of other code. # Usage: # java -jar proguard.jar @annotations.pro -libraryjars annotations.jar ... # # Note that the other input/output options still have to be specified. # If you specify them in a separate file, you can simply include this file: # -include annotations.pro # # You can add any other options that are required. For instance, if you are # processing a library, you can still include the options from library.pro. # The annotations are defined in the accompanying jar. For now, we'll start # with these. You can always define your own annotations, if necessary. -libraryjars annotations.jar # The following annotations can be specified with classes and with class # members. # @Keep specifies not to shrink, optimize, or obfuscate the annotated class # or class member as an entry point. -keep @proguard.annotation.Keep class * -keepclassmembers class * { @proguard.annotation.Keep *; } # @KeepName specifies not to optimize or obfuscate the annotated class or # class member as an entry point. -keepnames @proguard.annotation.KeepName class * -keepclassmembernames class * { @proguard.annotation.KeepName *; } # The following annotations can only be specified with classes. # @KeepImplementations and @KeepPublicImplementations specify to keep all, # resp. all public, implementations or extensions of the annotated class as # entry points. Note the extension of the java-like syntax, adding annotations # before the (wild-carded) interface name. -keep class * implements @proguard.annotation.KeepImplementations * -keep public class * implements @proguard.annotation.KeepPublicImplementations * # @KeepApplication specifies to keep the annotated class as an application, # together with its main method. -keepclasseswithmembers @proguard.annotation.KeepApplication public class * { public static void main(java.lang.String[]); } # @KeepClassMembers, @KeepPublicClassMembers, and # @KeepPublicProtectedClassMembers specify to keep all, all public, resp. # all public or protected, class members of the annotated class from being # shrunk, optimized, or obfuscated as entry points. -keepclassmembers @proguard.annotation.KeepClassMembers class * { *; } -keepclassmembers @proguard.annotation.KeepPublicClassMembers class * { public *; } -keepclassmembers @proguard.annotation.KeepPublicProtectedClassMembers class * { public protected *; } # @KeepClassMemberNames, @KeepPublicClassMemberNames, and # @KeepPublicProtectedClassMemberNames specify to keep all, all public, resp. # all public or protected, class members of the annotated class from being # optimized or obfuscated as entry points. -keepclassmembernames @proguard.annotation.KeepClassMemberNames class * { *; } -keepclassmembernames @proguard.annotation.KeepPublicClassMemberNames class * { public *; } -keepclassmembernames @proguard.annotation.KeepPublicProtectedClassMemberNames class * { public protected *; } # @KeepGettersSetters and @KeepPublicGettersSetters specify to keep all, resp. # all public, getters and setters of the annotated class from being shrunk, # optimized, or obfuscated as entry points. -keepclassmembers @proguard.annotation.KeepGettersSetters class * { void set*(***); void set*(int, ***); boolean is*(); boolean is*(int); *** get*(); *** get*(int); } -keepclassmembers @proguard.annotation.KeepPublicGettersSetters class * { public void set*(***); public void set*(int, ***); public boolean is*(); public boolean is*(int); public *** get*(); public *** get*(int); } ================================================ FILE: annotations/build.gradle ================================================ plugins { id 'java' id 'maven-publish' } sourceSets.main { java { srcDirs = ['src'] } resources { srcDirs = ['src'] include '**/*.properties' include '**/*.gif' include '**/*.png' include '**/*.pro' } } afterEvaluate { publishing { publications.getByName(project.name) { pom { description = 'Java annotations to configure ProGuard, the free shrinker, optimizer, obfuscator, and preverifier for Java bytecode' } } } } ================================================ FILE: annotations/src/proguard/annotation/Keep.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies not to optimize or obfuscate the annotated class or * class member as an entry point. * * @author Eric Lafortune */ @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR }) @Retention(RetentionPolicy.CLASS) @Documented public @interface Keep {} ================================================ FILE: annotations/src/proguard/annotation/KeepApplication.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep the annotated class as an application, * together with its a main method. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepApplication {} ================================================ FILE: annotations/src/proguard/annotation/KeepClassMemberNames.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep all class members of the annotated class * from being optimized or obfuscated as entry points. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepClassMemberNames {} ================================================ FILE: annotations/src/proguard/annotation/KeepClassMembers.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep all class members of the annotated class * from being shrunk, optimized, or obfuscated as entry points. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepClassMembers {} ================================================ FILE: annotations/src/proguard/annotation/KeepGettersSetters.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep all getters and setters of the annotated * class from being shrunk, optimized, or obfuscated as entry points. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepGettersSetters {} ================================================ FILE: annotations/src/proguard/annotation/KeepImplementations.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep all implementations or extensions of the * annotated class as entry points. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepImplementations {} ================================================ FILE: annotations/src/proguard/annotation/KeepName.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies not to optimize or obfuscate the annotated class or * class member as an entry point. * * @author Eric Lafortune */ @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepName {} ================================================ FILE: annotations/src/proguard/annotation/KeepPublicClassMemberNames.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep all public class members of the annotated * class from being optimized or obfuscated as entry points. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepPublicClassMemberNames {} ================================================ FILE: annotations/src/proguard/annotation/KeepPublicClassMembers.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep all public class members of the annotated * class from being shrunk, optimized, or obfuscated as entry points. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepPublicClassMembers {} ================================================ FILE: annotations/src/proguard/annotation/KeepPublicGettersSetters.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep all public getters and setters of the * annotated class from being shrunk, optimized, or obfuscated as entry points. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepPublicGettersSetters {} ================================================ FILE: annotations/src/proguard/annotation/KeepPublicImplementations.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep all public implementations or extensions * of the annotated class as entry points. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepPublicImplementations {} ================================================ FILE: annotations/src/proguard/annotation/KeepPublicProtectedClassMemberNames.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep all public or protected class members of * the annotated class from being optimized or obfuscated as entry points. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepPublicProtectedClassMemberNames {} ================================================ FILE: annotations/src/proguard/annotation/KeepPublicProtectedClassMembers.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. */ package proguard.annotation; import java.lang.annotation.*; /** * This annotation specifies to keep all public or protected class members of * the annotated class from being shrunk, optimized, or obfuscated as entry * points. * * @author Eric Lafortune */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) @Documented public @interface KeepPublicProtectedClassMembers {} ================================================ FILE: ant/build.gradle ================================================ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { id 'com.github.johnrengelman.shadow' id 'java' id 'maven-publish' } repositories { mavenCentral() } sourceSets.main { java { srcDirs = ['src'] include '**/*.java' } resources { srcDirs = ['src'] include '**/*.properties' include '**/*.gif' include '**/*.png' include '**/*.pro' } } dependencies { implementation project(':base') implementation 'org.apache.ant:ant:1.9.7' } task fatJar(type: ShadowJar) { destinationDirectory.set(file("$rootDir/lib")) archiveFileName.set('proguard-ant.jar') from sourceSets.main.output configurations = [project.configurations.runtimeClasspath] manifest { attributes( 'Manifest-Version': '1.0', 'Multi-Release': true, 'Implementation-Version': archiveVersion.get()) } } assemble.dependsOn fatJar afterEvaluate { publishing { publications.getByName(project.name) { pom { description = 'Ant plugin for ProGuard, the free shrinker, optimizer, obfuscator, and preverifier for Java bytecode' } } } } ================================================ FILE: ant/src/proguard/ant/ClassPathElement.java ================================================ /* * 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.ant; import org.apache.tools.ant.*; import org.apache.tools.ant.types.*; import proguard.*; import proguard.util.ListUtil; import java.io.File; /** * This FileSet represents a class path entry (or a set of class path entries) * in Ant. * * @author Eric Lafortune */ public class ClassPathElement extends Path { private String filter; private String apkFilter; private String jarFilter; private String aarFilter; private String warFilter; private String earFilter; private String zipFilter; /** * @see Path#Path(Project) */ public ClassPathElement(Project project) { super(project); } /** * Adds the contents of this class path element to the given class path. * @param classPath the class path to be extended. * @param output specifies whether this is an output entry or not. */ public void appendClassPathEntriesTo(ClassPath classPath, boolean output) { File baseDir = getProject().getBaseDir(); String[] fileNames; if (isReference()) { // Get the referenced path or file set. Object referencedObject = getCheckedRef(DataType.class, DataType.class.getName()); if (referencedObject instanceof Path) { Path path = (Path)referencedObject; // Get the names of the files in the referenced path. fileNames = path.list(); } else if (referencedObject instanceof AbstractFileSet) { AbstractFileSet fileSet = (AbstractFileSet)referencedObject; // Get the names of the existing input files in the referenced file set. DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject()); baseDir = scanner.getBasedir(); fileNames = scanner.getIncludedFiles(); } else { throw new BuildException("The refid attribute doesn't point to a element or a element"); } } else { // Get the names of the files in this path. fileNames = list(); } if (output) { if (fileNames.length != 1) { throw new BuildException("The element must specify exactly one file or directory ["+fileNames.length+"]"); } } //else //{ // if (fileNames.length < 1) // { // throw new BuildException("The element must specify at least one file or directory"); // } //} for (int index = 0; index < fileNames.length; index++) { // Create a new class path entry, with the proper file name and // any filters. String fileName = fileNames[index]; File file = new File(fileName); ClassPathEntry entry = new ClassPathEntry(file.isAbsolute() ? file : new File(baseDir, fileName), output); entry.setFilter(ListUtil.commaSeparatedList(filter)); entry.setApkFilter(ListUtil.commaSeparatedList(apkFilter)); entry.setJarFilter(ListUtil.commaSeparatedList(jarFilter)); entry.setAarFilter(ListUtil.commaSeparatedList(aarFilter)); entry.setWarFilter(ListUtil.commaSeparatedList(warFilter)); entry.setEarFilter(ListUtil.commaSeparatedList(earFilter)); entry.setZipFilter(ListUtil.commaSeparatedList(zipFilter)); // Add it to the class path. classPath.add(entry); } } // Ant task attributes. /** * @deprecated Use {@link #setLocation(File)} instead. */ public void setFile(File file) { setLocation(file); } /** * @deprecated Use {@link #setLocation(File)} instead. */ public void setDir(File file) { setLocation(file); } /** * @deprecated Use {@link #setLocation(File)} instead. */ public void setName(File file) { setLocation(file); } public void setFilter(String filter) { this.filter = filter; } public void setApkfilter(String apkFilter) { this.apkFilter = apkFilter; } public void setJarfilter(String jarFilter) { this.jarFilter = jarFilter; } public void setAarfilter(String aarFilter) { this.aarFilter = aarFilter; } public void setWarfilter(String warFilter) { this.warFilter = warFilter; } public void setEarfilter(String earFilter) { this.earFilter = earFilter; } public void setZipfilter(String zipFilter) { this.zipFilter = zipFilter; } } ================================================ FILE: ant/src/proguard/ant/ClassSpecificationElement.java ================================================ /* * 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.ant; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.types.DataType; import proguard.*; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import java.util.*; /** * This DataType represents a class specification in Ant. * * @author Eric Lafortune */ public class ClassSpecificationElement extends DataType { private static final String ANY_CLASS_KEYWORD = "*"; private String access; private String annotation; private String type; private String name; private String extendsAnnotation; private String extends_; private List fieldSpecifications = new ArrayList(); private List methodSpecifications = new ArrayList(); /** * Adds the contents of this class specification element to the given list. * @param classSpecifications the class specifications to be extended. */ public void appendTo(List classSpecifications) { // Get the referenced file set, or else this one. ClassSpecificationElement classSpecificationElement = isReference() ? (ClassSpecificationElement)getCheckedRef(this.getClass(), this.getClass().getName()) : this; ClassSpecification classSpecification = createClassSpecification(classSpecificationElement); // Add it to the list. classSpecifications.add(classSpecification); } /** * Creates a new class specification corresponding to the contents of this * class specification element. */ protected ClassSpecification createClassSpecification(ClassSpecificationElement classSpecificationElement) { String access = classSpecificationElement.access; String annotation = classSpecificationElement.annotation; String type = classSpecificationElement.type; String name = classSpecificationElement.name; String extendsAnnotation = classSpecificationElement.extendsAnnotation; String extends_ = classSpecificationElement.extends_; // For backward compatibility, allow a single "*" wildcard to match // any class. if (name != null && name.equals(ANY_CLASS_KEYWORD)) { name = null; } ClassSpecification classSpecification = new ClassSpecification(null, requiredAccessFlags(true, access, type), requiredAccessFlags(false, access, type), annotation != null ? ClassUtil.internalType(annotation) : null, name != null ? ClassUtil.internalClassName(name) : null, extendsAnnotation != null ? ClassUtil.internalType(extendsAnnotation) : null, extends_ != null ? ClassUtil.internalClassName(extends_) : null); for (int index = 0; index < fieldSpecifications.size(); index++) { classSpecification.addField((MemberSpecification)fieldSpecifications.get(index)); } for (int index = 0; index < methodSpecifications.size(); index++) { classSpecification.addMethod((MemberSpecification)methodSpecifications.get(index)); } return classSpecification; } // Ant task attributes. public void setAccess(String access) { this.access = access; } public void setAnnotation(String annotation) { this.annotation = annotation; } public void setType(String type) { this.type = type; } public void setName(String name) { this.name = name; } public void setExtendsannotation(String extendsAnnotation) { this.extendsAnnotation = extendsAnnotation; } public void setExtends(String extends_) { this.extends_ = extends_; } public void setImplements(String implements_) { this.extends_ = implements_; } // Ant task nested elements. public void addConfiguredField(MemberSpecificationElement memberSpecificationElement) { if (fieldSpecifications == null) { fieldSpecifications = new ArrayList(); } memberSpecificationElement.appendTo(fieldSpecifications, false, false); } public void addConfiguredMethod(MemberSpecificationElement memberSpecificationElement) { if (methodSpecifications == null) { methodSpecifications = new ArrayList(); } memberSpecificationElement.appendTo(methodSpecifications, true, false); } public void addConfiguredConstructor(MemberSpecificationElement memberSpecificationElement) { if (methodSpecifications == null) { methodSpecifications = new ArrayList(); } memberSpecificationElement.appendTo(methodSpecifications, true, true); } // Small utility methods. private int requiredAccessFlags(boolean set, String access, String type) throws BuildException { int accessFlags = 0; if (access != null) { StringTokenizer tokenizer = new StringTokenizer(access, " ,"); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.startsWith("!") ^ set) { String strippedToken = token.startsWith("!") ? token.substring(1) : token; int accessFlag = strippedToken.equals(JavaAccessConstants.PUBLIC) ? AccessConstants.PUBLIC : strippedToken.equals(JavaAccessConstants.FINAL) ? AccessConstants.FINAL : strippedToken.equals(JavaAccessConstants.ABSTRACT) ? AccessConstants.ABSTRACT : strippedToken.equals(JavaAccessConstants.SYNTHETIC) ? AccessConstants.SYNTHETIC : strippedToken.equals(JavaAccessConstants.ANNOTATION) ? AccessConstants.ANNOTATION : 0; if (accessFlag == 0) { throw new BuildException("Incorrect class access modifier ["+strippedToken+"]"); } accessFlags |= accessFlag; } } } if (type != null && (type.startsWith("!") ^ set)) { int accessFlag = type.equals("class") ? 0 : type.equals( JavaAccessConstants.INTERFACE) || type.equals("!" + JavaAccessConstants.INTERFACE) ? AccessConstants.INTERFACE : type.equals( JavaAccessConstants.ENUM) || type.equals("!" + JavaAccessConstants.ENUM) ? AccessConstants.ENUM : -1; if (accessFlag == -1) { throw new BuildException("Incorrect class type ["+type+"]"); } accessFlags |= accessFlag; } return accessFlags; } } ================================================ FILE: ant/src/proguard/ant/ConfigurationElement.java ================================================ /* * 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.ant; import org.apache.tools.ant.*; import org.apache.tools.ant.types.*; import proguard.*; import java.io.*; import java.util.Properties; /** * This DataType represents a reference to an XML-style ProGuard configuration * in Ant, or a file set of ProGuard-style configuration files. * * @author Eric Lafortune */ public class ConfigurationElement extends FileSet { /** * Adds the contents of this configuration element to the given * configuration. * @param configuration the configuration to be extended. */ public void appendTo(Configuration configuration) { File baseDir; String[] fileNames; if (isReference()) { // Get the referenced path or file set. Object referencedObject = getCheckedRef(Object.class, Object.class.getName()); if (referencedObject instanceof ConfigurationTask) { // The reference doesn't point to a file set, but to a // configuration task. ConfigurationTask configurationTask = (ConfigurationTask)referencedObject; // Append the contents of the referenced configuration to the // current configuration. configurationTask.appendTo(configuration); return; } else if (referencedObject instanceof AbstractFileSet) { AbstractFileSet fileSet = (AbstractFileSet)referencedObject; // Get the names of the existing input files in the referenced file set. DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject()); baseDir = scanner.getBasedir(); fileNames = scanner.getIncludedFiles(); } else { throw new BuildException("The refid attribute doesn't point to a element or a element"); } } else { // Get the names of the existing input files in the referenced file set. DirectoryScanner scanner = getDirectoryScanner(getProject()); baseDir = scanner.getBasedir(); fileNames = scanner.getIncludedFiles(); } // Get the combined system properties and Ant properties, for // replacing ProGuard-style properties ('<...>'). Properties properties = new Properties(); properties.putAll(getProject().getProperties()); try { // Append the contents of the configuration files to the current // configuration. for (int index = 0; index < fileNames.length; index++) { File configurationFile = new File(baseDir, fileNames[index]); try (ConfigurationParser parser = new ConfigurationParser(configurationFile, properties)) { parser.parse(configuration); } catch (ParseException ex) { throw new BuildException(ex.getMessage()); } } } catch (IOException ex) { throw new BuildException(ex.getMessage()); } } } ================================================ FILE: ant/src/proguard/ant/ConfigurationTask.java ================================================ /* * 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.ant; import org.apache.tools.ant.*; import proguard.*; import java.io.IOException; import java.util.*; /** * This Task allows to define a ProGuard configuration from Ant. * * @author Eric Lafortune */ public class ConfigurationTask extends Task { protected final Configuration configuration = new Configuration(); /** * Adds the contents of this configuration task to the given configuration. * @param configuration the configuration to be extended. */ public void appendTo(Configuration configuration) { // Append all of these configuration entries to the given configuration. configuration.programJars = extendClassPath(configuration.programJars, this.configuration.programJars); configuration.libraryJars = extendClassPath(configuration.libraryJars, this.configuration.libraryJars); configuration.keep = extendClassSpecifications(configuration.keep, this.configuration.keep); configuration.keepDirectories = extendList(configuration.keepDirectories, this.configuration.keepDirectories); configuration.whyAreYouKeeping = extendClassSpecifications(configuration.whyAreYouKeeping, this.configuration.whyAreYouKeeping); configuration.optimizations = extendClassSpecifications(configuration.optimizations, this.configuration.optimizations); configuration.assumeNoSideEffects = extendClassSpecifications(configuration.assumeNoSideEffects, this.configuration.assumeNoSideEffects); configuration.assumeNoExternalSideEffects = extendClassSpecifications(configuration.assumeNoExternalSideEffects, this.configuration.assumeNoExternalSideEffects); configuration.assumeNoEscapingParameters = extendClassSpecifications(configuration.assumeNoEscapingParameters, this.configuration.assumeNoEscapingParameters); configuration.assumeNoExternalReturnValues = extendClassSpecifications(configuration.assumeNoExternalReturnValues, this.configuration.assumeNoExternalReturnValues); configuration.assumeValues = extendClassSpecifications(configuration.assumeValues, this.configuration.assumeValues); configuration.keepPackageNames = extendList(configuration.keepPackageNames, this.configuration.keepPackageNames); configuration.keepAttributes = extendList(configuration.keepAttributes, this.configuration.keepAttributes); configuration.adaptClassStrings = extendList(configuration.adaptClassStrings, this.configuration.adaptClassStrings); configuration.adaptResourceFileNames = extendList(configuration.adaptResourceFileNames, this.configuration.adaptResourceFileNames); configuration.adaptResourceFileContents = extendList(configuration.adaptResourceFileContents, this.configuration.adaptResourceFileContents); configuration.note = extendList(configuration.note, this.configuration.note); configuration.warn = extendList(configuration.warn, this.configuration.warn); } // Ant task nested elements. public void addConfiguredInjar(ClassPathElement classPathElement) { configuration.programJars = extendClassPath(configuration.programJars, classPathElement, false); } public void addConfiguredOutjar(ClassPathElement classPathElement) { configuration.programJars = extendClassPath(configuration.programJars, classPathElement, true); } public void addConfiguredLibraryjar(ClassPathElement classPathElement) { configuration.libraryJars = extendClassPath(configuration.libraryJars, classPathElement, false); } public void addConfiguredKeepdirectory(FilterElement filterElement) { configuration.keepDirectories = extendFilter(configuration.keepDirectories, filterElement); } public void addConfiguredKeepdirectories(FilterElement filterElement) { configuration.keepDirectories = extendFilter(configuration.keepDirectories, filterElement); } public void addConfiguredKeep(KeepSpecificationElement keepSpecificationElement) { configuration.keep = extendKeepSpecifications(configuration.keep, keepSpecificationElement, true, true, false); } public void addConfiguredKeepclassmembers(KeepSpecificationElement keepSpecificationElement) { configuration.keep = extendKeepSpecifications(configuration.keep, keepSpecificationElement, false, true, false); } public void addConfiguredKeepclasseswithmembers(KeepSpecificationElement keepSpecificationElement) { configuration.keep = extendKeepSpecifications(configuration.keep, keepSpecificationElement, true, true, true); } public void addConfiguredKeepnames(KeepSpecificationElement keepSpecificationElement) { // Set the shrinking flag, based on the name (backward compatibility). keepSpecificationElement.setAllowshrinking(true); configuration.keep = extendKeepSpecifications(configuration.keep, keepSpecificationElement, true, true, false); } public void addConfiguredKeepclassmembernames(KeepSpecificationElement keepSpecificationElement) { // Set the shrinking flag, based on the name (backward compatibility). keepSpecificationElement.setAllowshrinking(true); configuration.keep = extendKeepSpecifications(configuration.keep, keepSpecificationElement, false, true, false); } public void addConfiguredKeepclasseswithmembernames(KeepSpecificationElement keepSpecificationElement) { // Set the shrinking flag, based on the name (backward compatibility). keepSpecificationElement.setAllowshrinking(true); configuration.keep = extendKeepSpecifications(configuration.keep, keepSpecificationElement, true, true, true); } public void addConfiguredWhyareyoukeeping(ClassSpecificationElement classSpecificationElement) { configuration.whyAreYouKeeping = extendClassSpecifications(configuration.whyAreYouKeeping, classSpecificationElement); } public void addConfiguredAssumenosideeffects(ClassSpecificationElement classSpecificationElement) { configuration.assumeNoSideEffects = extendClassSpecifications(configuration.assumeNoSideEffects, classSpecificationElement); } public void addConfiguredAssumenoexternalsideeffects(ClassSpecificationElement classSpecificationElement) { configuration.assumeNoExternalSideEffects = extendClassSpecifications(configuration.assumeNoExternalSideEffects, classSpecificationElement); } public void addConfiguredAssumenoescapingparameters(ClassSpecificationElement classSpecificationElement) { configuration.assumeNoEscapingParameters = extendClassSpecifications(configuration.assumeNoEscapingParameters, classSpecificationElement); } public void addConfiguredAssumenoexternalreturnvalues(ClassSpecificationElement classSpecificationElement) { configuration.assumeNoExternalReturnValues = extendClassSpecifications(configuration.assumeNoExternalReturnValues, classSpecificationElement); } public void addConfiguredAssumevalues(ClassSpecificationElement classSpecificationElement) { configuration.assumeValues = extendClassSpecifications(configuration.assumeValues, classSpecificationElement); } public void addConfiguredOptimizations(FilterElement filterElement) { addConfiguredOptimization(filterElement); } public void addConfiguredOptimization(FilterElement filterElement) { configuration.optimizations = extendFilter(configuration.optimizations, filterElement); } public void addConfiguredKeeppackagename(FilterElement filterElement) { configuration.keepPackageNames = extendFilter(configuration.keepPackageNames, filterElement, true); } public void addConfiguredKeeppackagenames(FilterElement filterElement) { configuration.keepPackageNames = extendFilter(configuration.keepPackageNames, filterElement, true); } public void addConfiguredKeepattributes(FilterElement filterElement) { addConfiguredKeepattribute(filterElement); } public void addConfiguredKeepattribute(FilterElement filterElement) { configuration.keepAttributes = extendFilter(configuration.keepAttributes, filterElement); } public void addConfiguredAdaptclassstrings(FilterElement filterElement) { configuration.adaptClassStrings = extendFilter(configuration.adaptClassStrings, filterElement, true); } public void addConfiguredAdaptresourcefilenames(FilterElement filterElement) { configuration.adaptResourceFileNames = extendFilter(configuration.adaptResourceFileNames, filterElement); } public void addConfiguredAdaptresourcefilecontents(FilterElement filterElement) { configuration.adaptResourceFileContents = extendFilter(configuration.adaptResourceFileContents, filterElement); } public void addConfiguredDontnote(FilterElement filterElement) { configuration.note = extendFilter(configuration.note, filterElement, true); } public void addConfiguredDontwarn(FilterElement filterElement) { configuration.warn = extendFilter(configuration.warn, filterElement, true); } public void addConfiguredConfiguration(ConfigurationElement configurationElement) { configurationElement.appendTo(configuration); } // Implementations for Task. public void addText(String text) throws BuildException { try { Project project = getProject(); // Replace Ant-style properties ('${...}'). String arg = project.replaceProperties(text); // Get the combined system properties and Ant properties, for // replacing ProGuard-style properties ('<...>'). Properties properties = new Properties(); properties.putAll(project.getProperties()); try (ConfigurationParser parser = new ConfigurationParser(arg, "embedded configuration", project.getBaseDir(), properties)) { parser.parse(configuration); } catch (ParseException ex) { throw new BuildException(ex.getMessage()); } } catch (IOException ex) { throw new BuildException(ex.getMessage()); } } // Small utility methods. private ClassPath extendClassPath(ClassPath classPath, ClassPathElement classPathElement, boolean output) { if (classPath == null) { classPath = new ClassPath(); } classPathElement.appendClassPathEntriesTo(classPath, output); return classPath; } private ClassPath extendClassPath(ClassPath classPath, ClassPath additionalClassPath) { if (additionalClassPath != null) { if (classPath == null) { classPath = new ClassPath(); } classPath.addAll(additionalClassPath); } return classPath; } private List extendKeepSpecifications(List keepSpecifications, KeepSpecificationElement keepSpecificationElement, boolean markClasses, boolean markClassMembers, boolean markClassesConditionally) { if (keepSpecifications == null) { keepSpecifications = new ArrayList(); } keepSpecificationElement.appendTo(keepSpecifications, markClasses, markClassMembers, markClassesConditionally); return keepSpecifications; } private List extendClassSpecifications(List classSpecifications, ClassSpecificationElement classSpecificationElement) { if (classSpecifications == null) { classSpecifications = new ArrayList(); } classSpecificationElement.appendTo(classSpecifications); return classSpecifications; } private List extendClassSpecifications(List classSpecifications, List additionalClassSpecifications) { if (additionalClassSpecifications != null) { if (classSpecifications == null) { classSpecifications = new ArrayList(); } classSpecifications.addAll(additionalClassSpecifications); } return classSpecifications; } private List extendFilter(List filter, FilterElement filterElement) { return extendFilter(filter, filterElement, false); } private List extendFilter(List filter, FilterElement filterElement, boolean internal) { if (filter == null) { filter = new ArrayList(); } filterElement.appendTo(filter, internal); return filter; } private List extendList(List list, List additionalList) { if (additionalList != null) { if (list == null) { list = new ArrayList(); } list.addAll(additionalList); } return list; } } ================================================ FILE: ant/src/proguard/ant/FilterElement.java ================================================ /* * 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.ant; import org.apache.tools.ant.types.DataType; import proguard.classfile.util.ClassUtil; import proguard.util.ListUtil; import java.util.List; /** * This DataType represents a name filter in Ant. * * @author Eric Lafortune */ public class FilterElement extends DataType { private String filter; /** * Adds the contents of this element to the given name filter. * @param filter the list of attributes to be extended. * @param internal specifies whether the filter string should be converted * to internal types. */ public void appendTo(List filter, boolean internal) { // Get the referenced element, or else this one. FilterElement filterElement = isReference() ? (FilterElement)getCheckedRef(this.getClass(), this.getClass().getName()) : this; String filterString = filterElement.filter; if (filterString == null) { // Clear the filter to keep all names. filter.clear(); } else { if (internal) { filterString = ClassUtil.internalClassName(filterString); } // Append the filter. filter.addAll(ListUtil.commaSeparatedList(filterString)); } } // Ant task attributes. public void setName(String name) { this.filter = name; } public void setFilter(String filter) { this.filter = filter; } } ================================================ FILE: ant/src/proguard/ant/KeepSpecificationElement.java ================================================ /* * 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.ant; import proguard.KeepClassSpecification; import java.util.List; /** * This DataType represents a class specification in Ant. * * @author Eric Lafortune */ public class KeepSpecificationElement extends ClassSpecificationElement { private boolean markDescriptorClasses; private boolean markCodeAttributes; private boolean allowShrinking; private boolean allowOptimization; private boolean allowObfuscation; /** * Adds the contents of this class specification element to the given list. * @param keepSpecifications the class specifications to be extended. * @param markClasses specifies whether to mark the classes. * @param markConditionally specifies whether to mark the classes * and class members conditionally. */ public void appendTo(List keepSpecifications, boolean markClasses, boolean markClassMembers, boolean markConditionally) { // Get the referenced file set, or else this one. KeepSpecificationElement keepSpecificationElement = isReference() ? (KeepSpecificationElement)getCheckedRef(this.getClass(), this.getClass().getName()) : this; KeepClassSpecification keepClassSpecification = new KeepClassSpecification(markClasses, markClassMembers, markConditionally, markDescriptorClasses, markCodeAttributes, allowShrinking, allowOptimization, allowObfuscation, null, createClassSpecification(keepSpecificationElement)); // Add it to the list. keepSpecifications.add(keepClassSpecification); } // Ant task attributes. public void setIncludedescriptorclasses(boolean markDescriptorClasses) { this.markDescriptorClasses = markDescriptorClasses; } public void setIncludecode(boolean markCodeAttributes) { this.markCodeAttributes = markCodeAttributes; } public void setAllowshrinking(boolean allowShrinking) { this.allowShrinking = allowShrinking; } public void setAllowoptimization(boolean allowOptimization) { this.allowOptimization = allowOptimization; } public void setAllowobfuscation(boolean allowObfuscation) { this.allowObfuscation = allowObfuscation; } } ================================================ FILE: ant/src/proguard/ant/MemberSpecificationElement.java ================================================ /* * 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.ant; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.types.DataType; import proguard.*; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import proguard.util.ListUtil; import java.util.*; /** * This DataType represents a class member specification in Ant. * * @author Eric Lafortune */ public class MemberSpecificationElement extends DataType { private String access; private String annotation; private String type; private String name; private String parameters; private String values; /** * Adds the contents of this class member specification element to the given * list. * @param memberSpecifications the class member specifications to be * extended. * @param isMethod specifies whether this specification * refers to a method. * @param isConstructor specifies whether this specification * refers to a constructor. */ public void appendTo(List memberSpecifications, boolean isMethod, boolean isConstructor) { // Get the referenced file set, or else this one. MemberSpecificationElement memberSpecificationElement = isReference() ? (MemberSpecificationElement)getCheckedRef(this.getClass(), this.getClass().getName()) : this; // Create a new class member specification. String access = memberSpecificationElement.access; String type = memberSpecificationElement.type; String annotation = memberSpecificationElement.annotation; String name = memberSpecificationElement.name; String parameters = memberSpecificationElement.parameters; String values = memberSpecificationElement.values; // Perform some basic conversions and checks on the attributes. if (annotation != null) { annotation = ClassUtil.internalType(annotation); } if (isMethod) { if (isConstructor) { if (type != null) { throw new BuildException("Type attribute not allowed in constructor specification ["+type+"]"); } if (parameters != null) { type = JavaTypeConstants.VOID; } if (values != null) { throw new BuildException("Values attribute not allowed in constructor specification ["+values+"]"); } name = ClassConstants.METHOD_NAME_INIT; } else if ((type != null) ^ (parameters != null)) { throw new BuildException("Type and parameters attributes must always be present in combination in method specification"); } } else { if (parameters != null) { throw new BuildException("Parameters attribute not allowed in field specification ["+parameters+"]"); } } if (values != null) { if (type == null) { throw new BuildException("Values attribute must be specified in combination with type attribute in class member specification ["+values+"]"); } } List parameterList = ListUtil.commaSeparatedList(parameters); String descriptor = parameters != null ? ClassUtil.internalMethodDescriptor(type, parameterList) : type != null ? ClassUtil.internalType(type) : null; MemberSpecification memberSpecification = values != null ? new MemberValueSpecification(requiredAccessFlags(true, access), requiredAccessFlags(false, access), annotation, name, descriptor, parseValues(type, ClassUtil.internalType(type), values)) : new MemberSpecification(requiredAccessFlags(true, access), requiredAccessFlags(false, access), annotation, name, descriptor); // Add it to the list. memberSpecifications.add(memberSpecification); } // Ant task attributes. public void setAccess(String access) { this.access = access; } public void setAnnotation(String annotation) { this.annotation = annotation; } public void setType(String type) { this.type = type; } public void setName(String name) { this.name = name; } public void setParameters(String parameters) { this.parameters = parameters; } /** * @deprecated Use {@link #setParameters(String)} instead. */ public void setParam(String parameters) { this.parameters = parameters; } public void setValues(String values) { this.values = values; } // Small utility methods. private int requiredAccessFlags(boolean set, String access) throws BuildException { int accessFlags = 0; if (access != null) { StringTokenizer tokenizer = new StringTokenizer(access, " ,"); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.startsWith("!") ^ set) { String strippedToken = token.startsWith("!") ? token.substring(1) : token; int accessFlag = strippedToken.equals(JavaAccessConstants.PUBLIC) ? AccessConstants.PUBLIC : strippedToken.equals(JavaAccessConstants.PRIVATE) ? AccessConstants.PRIVATE : strippedToken.equals(JavaAccessConstants.PROTECTED) ? AccessConstants.PROTECTED : strippedToken.equals(JavaAccessConstants.STATIC) ? AccessConstants.STATIC : strippedToken.equals(JavaAccessConstants.FINAL) ? AccessConstants.FINAL : strippedToken.equals(JavaAccessConstants.SYNCHRONIZED) ? AccessConstants.SYNCHRONIZED : strippedToken.equals(JavaAccessConstants.VOLATILE) ? AccessConstants.VOLATILE : strippedToken.equals(JavaAccessConstants.TRANSIENT) ? AccessConstants.TRANSIENT : strippedToken.equals(JavaAccessConstants.BRIDGE) ? AccessConstants.BRIDGE : strippedToken.equals(JavaAccessConstants.VARARGS) ? AccessConstants.VARARGS : strippedToken.equals(JavaAccessConstants.NATIVE) ? AccessConstants.NATIVE : strippedToken.equals(JavaAccessConstants.ABSTRACT) ? AccessConstants.ABSTRACT : strippedToken.equals(JavaAccessConstants.STRICT) ? AccessConstants.STRICT : strippedToken.equals(JavaAccessConstants.SYNTHETIC) ? AccessConstants.SYNTHETIC : 0; if (accessFlag == 0) { throw new BuildException("Incorrect class member access modifier ["+strippedToken+"]"); } accessFlags |= accessFlag; } } } return accessFlags; } /** * Parses the given string as a value or value range of the given primitive * type. For example, values "123" or "100..199" of type "int" ("I"). */ private Number[] parseValues(String externalType, String internalType, String string) throws BuildException { int rangeIndex = string.lastIndexOf(".."); return rangeIndex >= 0 ? new Number[] { parseValue(externalType, internalType, string.substring(0, rangeIndex)), parseValue(externalType, internalType, string.substring(rangeIndex + 2)) } : new Number[] { parseValue(externalType, internalType, string) }; } /** * Parses the given string as a value of the given primitive type. * For example, value "123" of type "int" ("I"). * For example, value "true" of type "boolean" ("Z"), returned as 1. */ private Number parseValue(String externalType, String internalType, String string) throws BuildException { try { switch (internalType.charAt(0)) { case TypeConstants.BOOLEAN: { return parseBoolean(string); } case TypeConstants.BYTE: case TypeConstants.CHAR: case TypeConstants.SHORT: case TypeConstants.INT: { return Integer.decode(string); } //case TypeConstants.LONG: //{ // return Long.decode(string); //} //case TypeConstants.FLOAT: //{ // return Float.valueOf(string); //} //case TypeConstants.DOUBLE: //{ // return Double.valueOf(string); //} default: { throw new BuildException("Can't handle '"+externalType+"' constant ["+string+"]"); } } } catch (NumberFormatException e) { throw new BuildException("Can't parse "+externalType+" constant ["+string+"]"); } } /** * Parses the given boolean string as an integer (0 or 1). */ private Integer parseBoolean(String string) throws BuildException { if ("false".equals(string)) { return Integer.valueOf(0); } else if ("true".equals(string)) { return Integer.valueOf(1); } else { throw new BuildException("Unknown boolean constant ["+string+"]"); } } } ================================================ FILE: ant/src/proguard/ant/ProGuardTask.java ================================================ /* * 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.ant; import org.apache.tools.ant.BuildException; import proguard.*; import proguard.classfile.util.ClassUtil; import java.io.*; import java.net.*; import java.util.*; /** * This Task allows to configure and run ProGuard from Ant. * * @author Eric Lafortune */ public class ProGuardTask extends ConfigurationTask { // Ant task attributes. public void setConfiguration(File configurationFile) throws BuildException { try { // Get the combined system properties and Ant properties, for // replacing ProGuard-style properties ('<...>'). Properties properties = new Properties(); properties.putAll(getProject().getProperties()); URL configUrl = ConfigurationElement.class.getResource(configurationFile.toString()); try (ConfigurationParser parser = configUrl != null ? new ConfigurationParser(configUrl, properties) : new ConfigurationParser(configurationFile, properties)) { parser.parse(configuration); } catch (ParseException e) { throw new BuildException(e.getMessage(), e); } } catch (IOException e) { throw new BuildException(e.getMessage(), e); } } /** * @deprecated Use the nested outjar element instead. */ public void setOutjar(String parameters) { throw new BuildException("Use the nested element instead of the 'outjar' attribute"); } public void setSkipnonpubliclibraryclasses(boolean skipNonPublicLibraryClasses) { configuration.skipNonPublicLibraryClasses = skipNonPublicLibraryClasses; } public void setSkipnonpubliclibraryclassmembers(boolean skipNonPublicLibraryClassMembers) { configuration.skipNonPublicLibraryClassMembers = skipNonPublicLibraryClassMembers; } public void setTarget(String target) { configuration.targetClassVersion = ClassUtil.internalClassVersion(target); if (configuration.targetClassVersion == 0) { throw new BuildException("Unsupported target '"+target+"'"); } } public void setForceprocessing(boolean forceProcessing) { configuration.lastModified = forceProcessing ? Long.MAX_VALUE : 0; } public void setPrintseeds(File printSeeds) { configuration.printSeeds = optionalFile(printSeeds); } public void setShrink(boolean shrink) { configuration.shrink = shrink; } public void setPrintusage(File printUsage) { configuration.printUsage = optionalFile(printUsage); } public void setOptimize(boolean optimize) { configuration.optimize = optimize; } public void setOptimizationpasses(int optimizationPasses) { configuration.optimizationPasses = optimizationPasses; } public void setAllowaccessmodification(boolean allowAccessModification) { configuration.allowAccessModification = allowAccessModification; } public void setMergeinterfacesaggressively(boolean mergeinterfacesaggressively) { configuration.mergeInterfacesAggressively = mergeinterfacesaggressively; } public void setObfuscate(boolean obfuscate) { configuration.obfuscate = obfuscate; } public void setPrintmapping(File printMapping) { configuration.printMapping = optionalFile(printMapping); } public void setApplymapping(File applyMapping) { configuration.applyMapping = resolvedFile(applyMapping); } public void setObfuscationdictionary(File obfuscationDictionary) { configuration.obfuscationDictionary = resolvedURL(obfuscationDictionary); } public void setClassobfuscationdictionary(File classObfuscationDictionary) { configuration.classObfuscationDictionary = resolvedURL(classObfuscationDictionary); } public void setPackageobfuscationdictionary(File packageObfuscationDictionary) { configuration.packageObfuscationDictionary = resolvedURL(packageObfuscationDictionary); } public void setOverloadaggressively(boolean overloadAggressively) { configuration.overloadAggressively = overloadAggressively; } public void setUseuniqueclassmembernames(boolean useUniqueClassMemberNames) { configuration.useUniqueClassMemberNames = useUniqueClassMemberNames; } public void setUsemixedcaseclassnames(boolean useMixedCaseClassNames) { configuration.useMixedCaseClassNames = useMixedCaseClassNames; } public void setFlattenpackagehierarchy(String flattenPackageHierarchy) { configuration.flattenPackageHierarchy = ClassUtil.internalClassName(flattenPackageHierarchy); } public void setRepackageclasses(String repackageClasses) { configuration.repackageClasses = ClassUtil.internalClassName(repackageClasses); } /** * @deprecated Use the repackageclasses attribute instead. */ public void setDefaultpackage(String defaultPackage) { configuration.repackageClasses = ClassUtil.internalClassName(defaultPackage); } public void setKeepparameternames(boolean keepParameterNames) { configuration.keepParameterNames = keepParameterNames; } public void setRenamesourcefileattribute(String newSourceFileAttribute) { configuration.newSourceFileAttribute = newSourceFileAttribute; } public void setPreverify(boolean preverify) { configuration.preverify = preverify; } public void setMicroedition(boolean microEdition) { configuration.microEdition = microEdition; } public void setAndroid(boolean android) { configuration.android = android; } public void setVerbose(boolean verbose) { configuration.verbose = verbose; } public void setNote(boolean note) { if (note) { // Switch on notes if they were completely disabled. if (configuration.note != null && configuration.note.isEmpty()) { configuration.note = null; } } else { // Switch off notes. configuration.note = new ArrayList(); } } public void setWarn(boolean warn) { if (warn) { // Switch on warnings if they were completely disabled. if (configuration.warn != null && configuration.warn.isEmpty()) { configuration.warn = null; } } else { // Switch off warnings. configuration.warn = new ArrayList(); } } public void setIgnorewarnings(boolean ignoreWarnings) { configuration.ignoreWarnings = ignoreWarnings; } public void setPrintconfiguration(File printConfiguration) { configuration.printConfiguration = optionalFile(printConfiguration); } public void setDump(File dump) { configuration.dump = optionalFile(dump); } public void setAddconfigurationdebugging(boolean addConfigurationDebugging) { configuration.addConfigurationDebugging = addConfigurationDebugging; } public void setKeepkotlinmetadata(boolean keepKotlinMetadata) { configuration.keepKotlinMetadata = keepKotlinMetadata; } // Implementations for Task. public void execute() throws BuildException { try { ProGuard proGuard = new ProGuard(configuration); proGuard.execute(); } catch (Exception e) { throw new BuildException(e.getMessage(), e); } } // Small utility methods. /** * Returns a file that is properly resolved with respect to the project * directory, or null or empty if its name is actually a * boolean flag. */ private File optionalFile(File file) { String fileName = file.getName(); return fileName.equalsIgnoreCase("false") || fileName.equalsIgnoreCase("no") || fileName.equalsIgnoreCase("off") ? null : fileName.equalsIgnoreCase("true") || fileName.equalsIgnoreCase("yes") || fileName.equalsIgnoreCase("on") ? Configuration.STD_OUT : resolvedFile(file); } /** * Returns a URL that is properly resolved with respect to the project * directory. */ private URL resolvedURL(File file) { try { return resolvedFile(file).toURI().toURL(); } catch (MalformedURLException e) { return null; } } /** * Returns a file that is properly resolved with respect to the project * directory. */ private File resolvedFile(File file) { return file.isAbsolute() ? file : new File(getProject().getBaseDir(), file.getName()); } } ================================================ FILE: ant/src/proguard/ant/package.html ================================================ This package contains the Ant task for ProGuard. ================================================ FILE: ant/src/proguard/ant/task.properties ================================================ proguard = proguard.ant.ProGuardTask proguardconfiguration = proguard.ant.ConfigurationTask ================================================ FILE: base/build.gradle ================================================ plugins { id 'java-library' id 'java-test-fixtures' id 'maven-publish' id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" id 'com.adarshr.test-logger' version '3.0.0' id 'de.jansauer.printcoverage' version '2.0.0' id 'jacoco' id "org.jlleitschuh.gradle.ktlint" version '10.2.1' } repositories { mavenCentral() } tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { kotlinOptions { jvmTarget = "${target}" } } dependencies { api "re.obfuscator:dprotect-core:${dprotectCoreVersion}" implementation "com.google.code.gson:gson:${gsonVersion}" implementation 'org.apache.logging.log4j:log4j-api:2.19.0' implementation 'org.apache.logging.log4j:log4j-core:2.19.0' implementation 'org.json:json:20220924' testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" testImplementation 'dev.zacsweers.kctfork:core:0.2.1' testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.4' // for kotest framework testImplementation 'io.kotest:kotest-assertions-core-jvm:5.5.4' // for kotest core jvm assertions testImplementation 'io.kotest:kotest-property-jvm:5.5.4' // for kotest property test testImplementation 'io.mockk:mockk:1.13.2' // for mocking testImplementation(testFixtures("com.guardsquare:proguard-core:9.0.8")) { exclude group: 'com.guardsquare', module: 'proguard-core' } } jar { manifest { attributes( 'Multi-Release': true, 'Implementation-Version': archiveVersion.get()) } } // Early access automatic downloads are not yet supported: // https://github.com/gradle/gradle/issues/14814 // But it will work if e.g. Java N-ea is pre-installed def javaVersionsForTest = 9..19 test { useJUnitPlatform() } task testAllJavaVersions() { testAllTask -> dependsOn(test) // the usual test runs on Java 8 javaVersionsForTest.each {version -> task("testJava$version", type: Test) { useJUnitPlatform() ignoreFailures = true // The version of bytebuddy used by mockk only supports Java 20 experimentally so far if (version == 20) systemProperty 'net.bytebuddy.experimental', true testAllTask.dependsOn(it) javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(version) } } } } jacocoTestReport { // Define which classes need to be monitored def sources = files(project.sourceSets.main.allSource.srcDirs) sourceDirectories.setFrom(sources) additionalSourceDirs.setFrom(sources) sourceDirectories.setFrom(sources) def classes = files(project.sourceSets.main.output.classesDirs) classDirectories.setFrom(classes) executionData.setFrom project.fileTree(dir: '.', include: '**/build/jacoco/*.exec') reports { xml.enabled true csv.enabled false html.destination file("${buildDir}/reports/coverage") } } afterEvaluate { publishing { publications.getByName(project.name) { pom { description = 'ProGuard is a free shrinker, optimizer, obfuscator, and preverifier for Java bytecode' } } } } ================================================ FILE: base/src/main/java/dprotect/ArithmeticObfuscationClassSpecification.java ================================================ package dprotect; import proguard.ClassSpecification; public class ArithmeticObfuscationClassSpecification extends ObfuscationClassSpecification { public final boolean skipFloat; public ArithmeticObfuscationClassSpecification(ClassSpecification classSpecification, Level level, boolean skipFloat) { super(classSpecification, level); this.skipFloat = false; } @Override public boolean equals(Object object) { if (object == null || this.getClass() != object.getClass()) { return false; } ArithmeticObfuscationClassSpecification other = (ArithmeticObfuscationClassSpecification)object; return this.skipFloat == other.skipFloat && super.equals(other); } @Override public int hashCode() { return (skipFloat ? 0 : 1) ^ super.hashCode(); } @Override public Object clone() { return super.clone(); } } ================================================ FILE: base/src/main/java/dprotect/CFObfuscationClassSpecification.java ================================================ package dprotect; import proguard.ClassSpecification; public class CFObfuscationClassSpecification extends ObfuscationClassSpecification { public CFObfuscationClassSpecification(ClassSpecification classSpecification, Level level) { super(classSpecification, level); } @Override public boolean equals(Object object) { if (object == null || this.getClass() != object.getClass()) { return false; } CFObfuscationClassSpecification other = (CFObfuscationClassSpecification)object; return super.equals(other); } @Override public int hashCode() { return super.hashCode(); } @Override public Object clone() { return super.clone(); } } ================================================ FILE: base/src/main/java/dprotect/ClassObfuSpecVisitorFactory.java ================================================ package dprotect; import proguard.ClassSpecificationVisitorFactory; import proguard.classfile.visitor.*; import java.util.List; import java.util.function.Function; public class ClassObfuSpecVisitorFactory extends ClassSpecificationVisitorFactory { public ClassPoolVisitor createClassPoolVisitor(List classSpecifications, Function classCreator, Function memberCreator) { MultiClassPoolVisitor multiClassPoolVisitor = new MultiClassPoolVisitor(); if (classSpecifications != null) { for (int index = 0; index < classSpecifications.size(); index++) { ObfuscationClassSpecification classSpecification = (ObfuscationClassSpecification)classSpecifications.get(index); ClassVisitor classVisitor = null; MemberVisitor memberVisitor = null; if (classCreator != null) { classVisitor = classCreator.apply(classSpecification); } if (memberCreator != null) { memberVisitor = memberCreator.apply(classSpecification); } multiClassPoolVisitor.addClassPoolVisitor( super.createClassPoolVisitor(classSpecification, classVisitor, memberVisitor, memberVisitor, null, null)); } } return multiClassPoolVisitor; } } ================================================ FILE: base/src/main/java/dprotect/Configuration.java ================================================ package dprotect; import proguard.ClassSpecification; import java.util.*; /** * The extended dProtect configuration based on Proguard * @author Romain Thomas */ public class Configuration extends proguard.Configuration { /////////////////////////////////////////////////////////////////////////// // Obfuscation options. /////////////////////////////////////////////////////////////////////////// /** * These options are used for deobfuscation purpose and * should not be used right now. */ public ClassSpecification deobfStrDecodeName; public String deobfStrXorKey; /** * List of obfuscation passes enbled */ public List obfuscations; /** * List of classes which must be constant-obfuscated */ public List obfuscateConstants; /** * List of classes which must be control-flow-obfuscated */ public List obfuscateControlFlow; /** * List of classes for which arithmetic operations must * be obfuscated */ public List obfuscateArithmetic; /** * List of classes for which strings must be obfuscated */ public List obfuscateStrings; /** * List of string that must also be obfuscated */ public List obfuscateStringsList; /** * Seed used for the random generator of obfuscation passes */ public Integer seed = null; } ================================================ FILE: base/src/main/java/dprotect/ConfigurationConstants.java ================================================ package dprotect; public class ConfigurationConstants extends proguard.ConfigurationConstants { public static final String OBFUSCATIONS = "-obfuscations"; public static final String OBFUSCATION_SEED = "-obfuscation-seed"; public static final String OBFUSCATE_STRING = "-obfuscate-strings"; public static final String OBFUSCATE_ARITHMETIC = "-obfuscate-arithmetic"; public static final String OBFUSCATE_CONSTANTS = "-obfuscate-constants"; public static final String OBFUSCATE_CONTROL_FLOW = "-obfuscate-control-flow"; public static final String OBFUCATION_LEVEL_LOW = "low"; public static final String OBFUCATION_LEVEL_MEDIUM = "medium"; public static final String OBFUCATION_LEVEL_HIGH = "high"; public static final String ARITHMETIC_OPT_SKIP_FLOAT = "skipfloat"; public static final String DEOBFUSCATE_XOR_STRINGS_DECODE_NAME = "-deobfuscate-xor-strings-decode-name"; public static final String DEOBFUSCATE_XOR_STRINGS_KEY = "-deobfuscate-xor-strings-key"; } ================================================ FILE: base/src/main/java/dprotect/ConfigurationParser.java ================================================ package dprotect; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.ClassSpecification; import proguard.ParseException; import proguard.WordReader; import proguard.classfile.JavaAccessConstants; import proguard.classfile.util.ClassUtil; import java.io.*; import java.net.*; import java.util.*; public class ConfigurationParser extends proguard.ConfigurationParser { private static final Logger logger = LogManager.getLogger(ConfigurationParser.class); public ConfigurationParser(String[] args, Properties properties) throws IOException { super(args, properties); } public ConfigurationParser(String[] args, File baseDir, Properties properties) throws IOException { super(args, baseDir, properties); } public ConfigurationParser(String lines, String description, File baseDir, Properties properties) throws IOException { super(lines, description, baseDir, properties); } public ConfigurationParser(File file) throws IOException { super(file); } public ConfigurationParser(File file, Properties properties) throws IOException { super(file, properties); } public ConfigurationParser(URL url, Properties properties) throws IOException { super(url, properties); } public ConfigurationParser(WordReader reader, Properties properties) throws IOException { super(reader, properties); } public void parse(Configuration configuration) throws ParseException, IOException { while (nextWord != null) { lastComments = reader.lastComments(); // First include directives. if (ConfigurationConstants.AT_DIRECTIVE .startsWith(nextWord) || ConfigurationConstants.INCLUDE_DIRECTIVE .startsWith(nextWord)) configuration.lastModified = super.parseIncludeArgument(configuration.lastModified); else if (ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE .startsWith(nextWord)) super.parseBaseDirectoryArgument(); // Then configuration options with or without arguments. else if (ConfigurationConstants.INJARS_OPTION .startsWith(nextWord)) configuration.programJars = super.parseClassPathArgument(configuration.programJars, false, true); else if (ConfigurationConstants.OUTJARS_OPTION .startsWith(nextWord)) configuration.programJars = super.parseClassPathArgument(configuration.programJars, true, false); else if (ConfigurationConstants.LIBRARYJARS_OPTION .startsWith(nextWord)) configuration.libraryJars = super.parseClassPathArgument(configuration.libraryJars, false, false); else if (ConfigurationConstants.RESOURCEJARS_OPTION .startsWith(nextWord)) throw new ParseException("The '-resourcejars' option is no longer supported. Please use the '-injars' option for all input"); else if (ConfigurationConstants.SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION .startsWith(nextWord)) configuration.skipNonPublicLibraryClasses = super.parseNoArgument(true); else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION .startsWith(nextWord)) configuration.skipNonPublicLibraryClasses = super.parseNoArgument(false); else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION.startsWith(nextWord)) configuration.skipNonPublicLibraryClassMembers = super.parseNoArgument(false); else if (ConfigurationConstants.TARGET_OPTION .startsWith(nextWord)) configuration.targetClassVersion = super.parseClassVersion(); else if (ConfigurationConstants.DONT_COMPRESS_OPTION .startsWith(nextWord)) configuration.dontCompress = super.parseCommaSeparatedList("file name", true, true, false, true, false, true, false, false, false, configuration.dontCompress); else if (ConfigurationConstants.ZIP_ALIGN_OPTION .startsWith(nextWord)) configuration.zipAlign = super.parseIntegerArgument(); else if (ConfigurationConstants.FORCE_PROCESSING_OPTION .startsWith(nextWord)) configuration.lastModified = super.parseNoArgument(Long.MAX_VALUE); else if (ConfigurationConstants.IF_OPTION .startsWith(nextWord)) configuration.keep = super.parseIfCondition(configuration.keep); else if (ConfigurationConstants.KEEP_OPTION .startsWith(nextWord)) configuration.keep = super.parseKeepClassSpecificationArguments(configuration.keep, true, true, false, false, false, null); else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION .startsWith(nextWord)) configuration.keep = super.parseKeepClassSpecificationArguments(configuration.keep, false, true, false, false, false, null); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION .startsWith(nextWord)) configuration.keep = super.parseKeepClassSpecificationArguments(configuration.keep, false, true, false, true, false, null); else if (ConfigurationConstants.KEEP_NAMES_OPTION .startsWith(nextWord)) configuration.keep = super.parseKeepClassSpecificationArguments(configuration.keep, true, true, false, false, true, null); else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.keep = super.parseKeepClassSpecificationArguments(configuration.keep, false, true, false, false, true, null); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.keep = super.parseKeepClassSpecificationArguments(configuration.keep, false, true, false, true, true, null); else if (ConfigurationConstants.KEEP_CODE_OPTION .startsWith(nextWord)) configuration.keep = super.parseKeepClassSpecificationArguments(configuration.keep, false, false, true, false, false, null); else if (ConfigurationConstants.PRINT_SEEDS_OPTION .startsWith(nextWord)) configuration.printSeeds = super.parseOptionalFile(); // After '-keep'. else if (ConfigurationConstants.KEEP_DIRECTORIES_OPTION .startsWith(nextWord)) configuration.keepDirectories = super.parseCommaSeparatedList("directory name", true, true, false, true, false, true, true, false, false, configuration.keepDirectories); else if (ConfigurationConstants.DONT_SHRINK_OPTION .startsWith(nextWord)) configuration.shrink = super.parseNoArgument(false); else if (ConfigurationConstants.PRINT_USAGE_OPTION .startsWith(nextWord)) configuration.printUsage = super.parseOptionalFile(); else if (ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION .startsWith(nextWord)) configuration.whyAreYouKeeping = super.parseClassSpecificationArguments(configuration.whyAreYouKeeping); else if (ConfigurationConstants.DONT_OPTIMIZE_OPTION .startsWith(nextWord)) configuration.optimize = super.parseNoArgument(false); else if (ConfigurationConstants.OPTIMIZATION_PASSES .startsWith(nextWord)) configuration.optimizationPasses = super.parseIntegerArgument(); else if (ConfigurationConstants.OPTIMIZATIONS .startsWith(nextWord)) configuration.optimizations = super.parseCommaSeparatedList("optimization name", true, false, false, false, false, true, false, false, false, configuration.optimizations); else if (ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION .startsWith(nextWord)) configuration.assumeNoSideEffects = super.parseAssumeClassSpecificationArguments(configuration.assumeNoSideEffects); else if (ConfigurationConstants.ASSUME_NO_EXTERNAL_SIDE_EFFECTS_OPTION .startsWith(nextWord)) configuration.assumeNoExternalSideEffects = super.parseAssumeClassSpecificationArguments(configuration.assumeNoExternalSideEffects); else if (ConfigurationConstants.ASSUME_NO_ESCAPING_PARAMETERS_OPTION .startsWith(nextWord)) configuration.assumeNoEscapingParameters = super.parseAssumeClassSpecificationArguments(configuration.assumeNoEscapingParameters); else if (ConfigurationConstants.ASSUME_NO_EXTERNAL_RETURN_VALUES_OPTION .startsWith(nextWord)) configuration.assumeNoExternalReturnValues = super.parseAssumeClassSpecificationArguments(configuration.assumeNoExternalReturnValues); else if (ConfigurationConstants.ASSUME_VALUES_OPTION .startsWith(nextWord)) configuration.assumeValues = super.parseAssumeClassSpecificationArguments(configuration.assumeValues); else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION .startsWith(nextWord)) configuration.allowAccessModification = super.parseNoArgument(true); else if (ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.mergeInterfacesAggressively = super.parseNoArgument(true); else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION .startsWith(nextWord)) configuration.obfuscate = super.parseNoArgument(false); else if (ConfigurationConstants.PRINT_MAPPING_OPTION .startsWith(nextWord)) configuration.printMapping = super.parseOptionalFile(); else if (ConfigurationConstants.APPLY_MAPPING_OPTION .startsWith(nextWord)) configuration.applyMapping = super.parseFile(); else if (ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.obfuscationDictionary = super.parseURL(); else if (ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.classObfuscationDictionary = super.parseURL(); else if (ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.packageObfuscationDictionary = super.parseURL(); else if (ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.overloadAggressively = super.parseNoArgument(true); else if (ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.useUniqueClassMemberNames = super.parseNoArgument(true); else if (ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION .startsWith(nextWord)) configuration.useMixedCaseClassNames = super.parseNoArgument(false); else if (ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION .startsWith(nextWord)) configuration.keepPackageNames = super.parseCommaSeparatedList("package name", true, true, false, false, true, false, false, true, false, configuration.keepPackageNames); else if (ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION .startsWith(nextWord)) configuration.flattenPackageHierarchy = ClassUtil.internalClassName(super.parseOptionalArgument()); else if (ConfigurationConstants.REPACKAGE_CLASSES_OPTION .startsWith(nextWord) || ConfigurationConstants.DEFAULT_PACKAGE_OPTION .startsWith(nextWord)) configuration.repackageClasses = ClassUtil.internalClassName(super.parseOptionalArgument()); else if (ConfigurationConstants.KEEP_ATTRIBUTES_OPTION .startsWith(nextWord)) configuration.keepAttributes = super.parseCommaSeparatedList("attribute name", true, true, false, false, true, false, false, false, false, configuration.keepAttributes); else if (ConfigurationConstants.KEEP_PARAMETER_NAMES_OPTION .startsWith(nextWord)) configuration.keepParameterNames = super.parseNoArgument(true); else if (ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION .startsWith(nextWord)) configuration.newSourceFileAttribute = super.parseOptionalArgument(); else if (ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION .startsWith(nextWord)) configuration.adaptClassStrings = super.parseCommaSeparatedList("class name", true, true, false, false, true, false, false, true, false, configuration.adaptClassStrings); else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION .startsWith(nextWord)) configuration.adaptResourceFileNames = super.parseCommaSeparatedList("resource file name", true, true, false, true, false, true, false, false, false, configuration.adaptResourceFileNames); else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION .startsWith(nextWord)) configuration.adaptResourceFileContents = super.parseCommaSeparatedList("resource file name", true, true, false, true, false, true, false, false, false, configuration.adaptResourceFileContents); else if (ConfigurationConstants.DONT_PROCESS_KOTLIN_METADATA .startsWith(nextWord)) configuration.dontProcessKotlinMetadata = parseNoArgument(true); else if (ConfigurationConstants.KEEP_KOTLIN_METADATA .startsWith(nextWord)) configuration.keepKotlinMetadata = parseKeepKotlinMetadata(); else if (ConfigurationConstants.DONT_PREVERIFY_OPTION .startsWith(nextWord)) configuration.preverify = super.parseNoArgument(false); else if (ConfigurationConstants.MICRO_EDITION_OPTION .startsWith(nextWord)) configuration.microEdition = super.parseNoArgument(true); else if (ConfigurationConstants.ANDROID_OPTION .startsWith(nextWord)) configuration.android = super.parseNoArgument(true); else if (ConfigurationConstants.KEY_STORE_OPTION .startsWith(nextWord)) configuration.keyStores = super.parseFiles(configuration.keyStores); else if (ConfigurationConstants.KEY_STORE_PASSWORD_OPTION .startsWith(nextWord)) configuration.keyStorePasswords = super.parseCommaSeparatedList("keystore password", true, false, false, false, false, false, true, false, false, configuration.keyStorePasswords); else if (ConfigurationConstants.KEY_ALIAS_OPTION .startsWith(nextWord)) configuration.keyAliases = super.parseCommaSeparatedList("key", true, false, false, false, false, false, true, false, false, configuration.keyAliases); else if (ConfigurationConstants.KEY_PASSWORD_OPTION .startsWith(nextWord)) configuration.keyPasswords = super.parseCommaSeparatedList("key password", true, false, false, false, false, false, true, false, false, configuration.keyPasswords); else if (ConfigurationConstants.VERBOSE_OPTION .startsWith(nextWord)) configuration.verbose = super.parseNoArgument(true); else if (ConfigurationConstants.DONT_NOTE_OPTION .startsWith(nextWord)) configuration.note = super.parseCommaSeparatedList("class name", true, true, false, false, true, false, false, true, false, configuration.note); else if (ConfigurationConstants.DONT_WARN_OPTION .startsWith(nextWord)) configuration.warn = super.parseCommaSeparatedList("class name", true, true, false, false, true, false, false, true, false, configuration.warn); else if (ConfigurationConstants.IGNORE_WARNINGS_OPTION .startsWith(nextWord)) configuration.ignoreWarnings = super.parseNoArgument(true); else if (ConfigurationConstants.PRINT_CONFIGURATION_OPTION .startsWith(nextWord)) configuration.printConfiguration = super.parseOptionalFile(); else if (ConfigurationConstants.DUMP_OPTION .startsWith(nextWord)) configuration.dump = super.parseOptionalFile(); else if (ConfigurationConstants.ADD_CONFIGURATION_DEBUGGING_OPTION .startsWith(nextWord)) configuration.addConfigurationDebugging = super.parseNoArgument(true); else if (ConfigurationConstants.OPTIMIZE_AGGRESSIVELY .startsWith(nextWord)) configuration.optimizeConservatively = parseNoArgument(false); else { parseObfuscationConfig(configuration); } } } public void parseObfuscationConfig(Configuration configuration) throws ParseException, IOException { if (ConfigurationConstants.DEOBFUSCATE_XOR_STRINGS_DECODE_NAME .startsWith(nextWord)) configuration.deobfStrDecodeName = super.parseClassSpecificationArguments(true, true, true); else if (ConfigurationConstants.DEOBFUSCATE_XOR_STRINGS_KEY .startsWith(nextWord)) configuration.deobfStrXorKey = super.parseOptionalArgument(); else if (ConfigurationConstants.OBFUSCATIONS .startsWith(nextWord)) configuration.obfuscations = super.parseCommaSeparatedList("obfuscations name", true, false, false, false, false, true, false, false, false, configuration.obfuscations); else if (ConfigurationConstants.OBFUSCATION_SEED .startsWith(nextWord)) configuration.seed = super.parseIntegerArgument(); else if (ConfigurationConstants.OBFUSCATE_STRING .startsWith(nextWord)) parseStringOpt(configuration); else if (ConfigurationConstants.OBFUSCATE_ARITHMETIC .startsWith(nextWord)) parseArithmeticOpt(configuration); else if (ConfigurationConstants.OBFUSCATE_CONSTANTS .startsWith(nextWord)) parseConstantsOpt(configuration); else if (ConfigurationConstants.OBFUSCATE_CONTROL_FLOW .startsWith(nextWord)) parseControlFlowOpt(configuration); else { throw new ParseException("Unknown option " + reader.locationDescription()); } } private void parseStringOpt(Configuration configuration) throws ParseException, IOException { configuration.obfuscateStringsList = new ArrayList(); super.readNextWord("", false, false, false); if (ConfigurationConstants.CLASS_KEYWORD.startsWith(nextWord) || JavaAccessConstants.INTERFACE.startsWith(nextWord) || JavaAccessConstants.ENUM.startsWith(nextWord)) { if (configuration.obfuscateStrings == null) { configuration.obfuscateStrings = new ArrayList(); } configuration.obfuscateStrings.add(super.parseClassSpecificationArguments(false, true, true)); } else { configuration.obfuscateStringsList = super.parseCommaSeparatedList("strings to obfuscate", /* readFirstWord */ false, /* allowEmptyList */ false, /* defaultIfEmpty */ null, /* expectClosingParenthesis */ false, /* isFileName */ false, /* checkJavaIdentifiers */ false, /* allowGenerics */ true, /* replaceSystemProperties */ false, /* replaceExternalClassNames */ false, /* replaceExternalTypes */ false, configuration.obfuscateStringsList); } } /* * Parse the -obfuscate-arithmetic option and its specifier. * The code of this pass is highly inspired from * proguard.ConfigurationParser.parseKeepClassSpecificationArguments */ private void parseArithmeticOpt(Configuration configuration) throws ParseException, IOException { boolean skipFloat = false; ObfuscationClassSpecification.Level level = ObfuscationClassSpecification.Level.NONE; while (true) { readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD + "', '" + JavaAccessConstants.INTERFACE + "', or '" + JavaAccessConstants.ENUM + "'", false, false, true); if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord)) { // Not a comma. Stop parsing the keep modifiers. break; } readNextWord("keyword '" + ConfigurationConstants.OBFUCATION_LEVEL_LOW + "', '" + ConfigurationConstants.OBFUCATION_LEVEL_MEDIUM + "', '" + ConfigurationConstants.OBFUCATION_LEVEL_HIGH + "', or '" + ConfigurationConstants.ARITHMETIC_OPT_SKIP_FLOAT + "'"); if (ConfigurationConstants.OBFUCATION_LEVEL_LOW.startsWith(nextWord)) { level = ObfuscationClassSpecification.Level.LOW; } else if (ConfigurationConstants.OBFUCATION_LEVEL_MEDIUM.startsWith(nextWord)) { level = ObfuscationClassSpecification.Level.MEDIUM; } else if (ConfigurationConstants.OBFUCATION_LEVEL_HIGH.startsWith(nextWord)) { level = ObfuscationClassSpecification.Level.HIGH; } else if (ConfigurationConstants.ARITHMETIC_OPT_SKIP_FLOAT.startsWith(nextWord)) { skipFloat = true; } else { throw new ParseException("Expecting keyword '" + ConfigurationConstants.OBFUCATION_LEVEL_LOW + "', '" + ConfigurationConstants.OBFUCATION_LEVEL_MEDIUM + "', '" + ConfigurationConstants.OBFUCATION_LEVEL_HIGH + "', or '" + ConfigurationConstants.ARITHMETIC_OPT_SKIP_FLOAT + "' before " + reader.locationDescription()); } } ClassSpecification classSpecification = parseClassSpecificationArguments(false, true, false); if (configuration.obfuscateArithmetic == null) { configuration.obfuscateArithmetic = new ArrayList(); } configuration.obfuscateArithmetic.add( new ArithmeticObfuscationClassSpecification(classSpecification, level, skipFloat)); } /* * Parse the -obfuscate-constants option. This option does not currently support extra modifiers * but the there is a free room for that. * * The code of this pass is highly inspired from * proguard.ConfigurationParser.parseKeepClassSpecificationArguments */ private void parseConstantsOpt(Configuration configuration) throws ParseException, IOException { ObfuscationClassSpecification.Level level = ObfuscationClassSpecification.Level.NONE; readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD + "', '" + JavaAccessConstants.INTERFACE + "', or '" + JavaAccessConstants.ENUM + "'", false, false, true); ClassSpecification classSpecification = parseClassSpecificationArguments(false, true, false); if (configuration.obfuscateConstants == null) { configuration.obfuscateConstants = new ArrayList(); } configuration.obfuscateConstants.add( new ConstantObfuscationClassSpecification(classSpecification, level)); } /* * Parse the -obfuscate-control-flow option. This option does not currently support extra modifiers * but the there is a free room for that. * * The code of this pass is highly inspired from * proguard.ConfigurationParser.parseKeepClassSpecificationArguments */ private void parseControlFlowOpt(Configuration configuration) throws ParseException, IOException { ObfuscationClassSpecification.Level level = ObfuscationClassSpecification.Level.NONE; readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD + "', '" + JavaAccessConstants.INTERFACE + "', or '" + JavaAccessConstants.ENUM + "'", false, false, true); ClassSpecification classSpecification = parseClassSpecificationArguments(false, true, false); if (configuration.obfuscateControlFlow == null) { configuration.obfuscateControlFlow = new ArrayList(); } configuration.obfuscateControlFlow.add( new CFObfuscationClassSpecification(classSpecification, level)); } } ================================================ FILE: base/src/main/java/dprotect/ConstantObfuscationClassSpecification.java ================================================ package dprotect; import proguard.ClassSpecification; public class ConstantObfuscationClassSpecification extends ObfuscationClassSpecification { public ConstantObfuscationClassSpecification(ClassSpecification classSpecification, Level level) { super(classSpecification, level); } @Override public boolean equals(Object object) { if (object == null || this.getClass() != object.getClass()) { return false; } ConstantObfuscationClassSpecification other = (ConstantObfuscationClassSpecification)object; return super.equals(other); } @Override public int hashCode() { return super.hashCode(); } @Override public Object clone() { return super.clone(); } } ================================================ FILE: base/src/main/java/dprotect/DProtect.java ================================================ package dprotect; import dprotect.obfuscation.CodeObfuscator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AfterInitConfigurationVerifier; import proguard.AppView; import proguard.ConfigurationVerifier; import proguard.ConfigurationWriter; import proguard.Dumper; import proguard.GPL; import proguard.Initializer; import proguard.InputReader; import proguard.KotlinMetadataAdapter; import proguard.OutputWriter; import proguard.SeedPrinter; import proguard.Targeter; import proguard.UpToDateChecker; import proguard.backport.Backporter; import proguard.classfile.editor.*; import proguard.classfile.pass.PrimitiveArrayConstantIntroducer; import proguard.classfile.util.*; import proguard.configuration.ConfigurationLoggingAdder; import proguard.configuration.InitialStateInfo; import proguard.evaluation.IncompleteClassHierarchyException; import proguard.logging.Logging; import proguard.mark.Marker; import proguard.obfuscate.NameObfuscationReferenceFixer; import proguard.obfuscate.ObfuscationPreparation; import proguard.obfuscate.Obfuscator; import proguard.obfuscate.ResourceFileNameAdapter; import proguard.optimize.LineNumberTrimmer; import proguard.optimize.Optimizer; import proguard.optimize.gson.GsonOptimizer; import proguard.optimize.peephole.LineNumberLinearizer; import proguard.pass.PassRunner; import proguard.preverify.*; import proguard.shrink.Shrinker; import proguard.strip.KotlinAnnotationStripper; import proguard.util.*; import proguard.util.kotlin.KotlinUnsupportedVersionChecker; import proguard.util.kotlin.asserter.KotlinMetadataVerifier; import java.io.*; /** * This class wraps the dProtect obfuscation pipeline. * This is almost an exact copy of Proguard.java except that it also * invokes the obfuscation pipeline: * * + CodeObfuscator codeObfuscator = new CodeObfuscator(configuration); * + passRunner.run(codeObfuscator, appView); */ public class DProtect { private static final Logger logger = LogManager.getLogger(DProtect.class); public static final String VERSION = "dProtect, version " + getVersion(); /** * A data object containing pass inputs in a centralized location. Passes can access and update the information * at any point in the pipeline. */ private final AppView appView; private final PassRunner passRunner; private final Configuration configuration; /** * Creates a new ProGuard object to process jars as specified by the given * configuration. */ public DProtect(Configuration configuration) { this.appView = new AppView(); this.passRunner = new PassRunner(); this.configuration = configuration; } /** * Performs all subsequent ProGuard operations. */ public void execute() throws Exception { Logging.configureVerbosity(configuration.verbose); logger.always().log(VERSION); try { checkGpl(); // Set the -keepkotlinmetadata option if necessary. if (!configuration.dontProcessKotlinMetadata) { configuration.keepKotlinMetadata = requiresKotlinMetadata(); } if (configuration.printConfiguration != null) { printConfiguration(); } checkConfiguration(); if (configuration.programJars.hasOutput()) { checkUpToDate(); } if (configuration.targetClassVersion != 0) { configuration.backport = true; } readInput(); if (configuration.shrink || configuration.optimize || configuration.obfuscate || configuration.preverify) { clearPreverification(); } if (configuration.printSeeds != null || configuration.backport || configuration.shrink || configuration.optimize || configuration.obfuscate || configuration.preverify || configuration.addConfigurationDebugging || configuration.keepKotlinMetadata) { initialize(); mark(); } checkConfigurationAfterInitialization(); if (configuration.addConfigurationDebugging) { // Remember the initial state of the program classpool and resource filepool // before shrinking / obfuscation / optimization. appView.initialStateInfo = new InitialStateInfo(appView.programClassPool); } if (configuration.keepKotlinMetadata) { stripKotlinMetadataAnnotations(); } if (configuration.optimize || configuration.obfuscate) { introducePrimitiveArrayConstants(); } if (configuration.backport) { backport(); } if (configuration.addConfigurationDebugging) { addConfigurationLogging(); } if (configuration.printSeeds != null) { printSeeds(); } if (configuration.preverify || configuration.android) { inlineSubroutines(); } if (configuration.shrink) { shrink(false); } // Create a matcher for filtering optimizations. StringMatcher filter = configuration.optimizations != null ? new ListParser(new NameParser()).parse(configuration.optimizations) : new ConstantMatcher(true); if (configuration.optimize && filter.matches(Optimizer.LIBRARY_GSON)) { optimizeGson(); } if (configuration.optimize) { optimize(); linearizeLineNumbers(); } if (configuration.obfuscate) { markObfuscation(); obfuscate(); } if (configuration.keepKotlinMetadata) { adaptKotlinMetadata(); } if (configuration.optimize || configuration.obfuscate) { expandPrimitiveArrayConstants(); } if (configuration.targetClassVersion != 0) { target(); } if (configuration.preverify) { preverify(); } // Trim line numbers after preverification as this might // also remove some instructions. if (configuration.optimize || configuration.preverify) { trimLineNumbers(); } if (configuration.shrink || configuration.optimize || configuration.obfuscate || configuration.preverify) { sortClassElements(); } if (configuration.programJars.hasOutput()) { writeOutput(); } if (configuration.dump != null) { dump(); } } catch (UpToDateChecker.UpToDateException ignore) {} catch (IncompleteClassHierarchyException e) { throw new RuntimeException( System.lineSeparator() + System.lineSeparator() + "It appears you are missing some classes resulting in an incomplete class hierarchy, " + System.lineSeparator() + "please refer to the troubleshooting page in the manual: " + System.lineSeparator() + "https://www.guardsquare.com/en/products/proguard/manual/troubleshooting#superclass" + System.lineSeparator() ); } } /** * Checks the GPL. */ private void checkGpl() { GPL.check(); } private boolean requiresKotlinMetadata() { return configuration.keepKotlinMetadata || (configuration.keep != null && configuration.keep.stream().anyMatch( keepClassSpecification -> ! keepClassSpecification.allowObfuscation && ! keepClassSpecification.allowShrinking && "kotlin/Metadata".equals(keepClassSpecification.className) )); } /** * Prints out the configuration that ProGuard is using. */ private void printConfiguration() throws IOException { PrintWriter pw = PrintWriterUtil.createPrintWriterOut(configuration.printConfiguration); try (ConfigurationWriter configurationWriter = new ConfigurationWriter(pw)) { configurationWriter.write(configuration); } } /** * Checks the configuration for conflicts and inconsistencies. */ private void checkConfiguration() throws IOException { new ConfigurationVerifier(configuration).check(); } /** * Checks whether the output is up-to-date. */ private void checkUpToDate() { new UpToDateChecker(configuration).check(); } /** * Reads the input class files. */ private void readInput() throws Exception { // Fill the program class pool and the library class pool. passRunner.run(new InputReader(configuration), appView); } /** * Clears any JSE preverification information from the program classes. */ private void clearPreverification() throws Exception { passRunner.run(new PreverificationClearer(), appView); } /** * Initializes the cross-references between all classes, performs some * basic checks, and shrinks the library class pool. */ private void initialize() throws Exception { if (configuration.keepKotlinMetadata) { passRunner.run(new KotlinUnsupportedVersionChecker(), appView); } passRunner.run(new Initializer(configuration), appView); if (configuration.keepKotlinMetadata && configuration.enableKotlinAsserter) { passRunner.run(new KotlinMetadataVerifier(configuration), appView); } } /** * Marks the classes, class members and attributes to be kept or encrypted, * by setting the appropriate access flags. */ private void mark() throws Exception { passRunner.run(new Marker(configuration), appView); } /** * Strips the Kotlin metadata annotation where possible. */ private void stripKotlinMetadataAnnotations() throws Exception { passRunner.run(new KotlinAnnotationStripper(configuration), appView); } /** * Checks the configuration after it has been initialized. */ private void checkConfigurationAfterInitialization() throws Exception { passRunner.run(new AfterInitConfigurationVerifier(configuration), appView); } /** * Replaces primitive array initialization code by primitive array constants. */ private void introducePrimitiveArrayConstants() throws Exception { passRunner.run(new PrimitiveArrayConstantIntroducer(), appView); } /** * Backports java language features to the specified target version. */ private void backport() throws Exception { passRunner.run(new Backporter(configuration), appView); } /** * Adds configuration logging code, providing suggestions on improving * the ProGuard configuration. */ private void addConfigurationLogging() throws Exception { passRunner.run(new ConfigurationLoggingAdder(), appView); } /** * Prints out classes and class members that are used as seeds in the * shrinking and obfuscation steps. */ private void printSeeds() throws Exception { passRunner.run(new SeedPrinter(configuration), appView); } /** * Performs the subroutine inlining step. */ private void inlineSubroutines() throws Exception { // Perform the actual inlining. passRunner.run(new SubroutineInliner(configuration), appView); } /** * Performs the shrinking step. */ private void shrink(boolean afterOptimizer) throws Exception { // Perform the actual shrinking. passRunner.run(new Shrinker(configuration, afterOptimizer), appView); if (configuration.keepKotlinMetadata && configuration.enableKotlinAsserter) { passRunner.run(new KotlinMetadataVerifier(configuration), appView); } } /** * Optimizes usages of the Gson library. */ private void optimizeGson() throws Exception { // Perform the Gson optimization. passRunner.run(new GsonOptimizer(configuration), appView); } /** * Performs the optimization step. */ private void optimize() throws Exception { Optimizer optimizer = new Optimizer(configuration); for (int optimizationPass = 0; optimizationPass < configuration.optimizationPasses; optimizationPass++) { // Perform the actual optimization. passRunner.run(optimizer, appView); // Shrink again, if we may. if (configuration.shrink) { shrink(true); } } } /** * Disambiguates the line numbers of all program classes, after * optimizations like method inlining and class merging. */ private void linearizeLineNumbers() throws Exception { passRunner.run(new LineNumberLinearizer(), appView); } /** * Marks the classes, class members and attributes which are set to * be obfuscated (from the user configuration) */ private void markObfuscation() throws Exception { passRunner.run(new dprotect.obfuscation.Marker(configuration), appView); } /** * Performs the obfuscation step. */ private void obfuscate() throws Exception { CodeObfuscator codeObfuscator = new CodeObfuscator(configuration); passRunner.run(codeObfuscator, appView); // Shrink again, if we may. if (configuration.shrink) { shrink(true); } passRunner.run(new ObfuscationPreparation(configuration), appView); // Perform the actual obfuscation. passRunner.run(new Obfuscator(configuration), appView); // Adapt resource file names that correspond to class names, if necessary. if (configuration.adaptResourceFileNames != null) { passRunner.run(new ResourceFileNameAdapter(configuration), appView); } // Fix the Kotlin modules so the filename matches and the class names match. passRunner.run(new NameObfuscationReferenceFixer(configuration), appView); if (configuration.keepKotlinMetadata && configuration.enableKotlinAsserter) { passRunner.run(new KotlinMetadataVerifier(configuration), appView); } } /** * Adapts Kotlin Metadata annotations. */ private void adaptKotlinMetadata() throws Exception { passRunner.run(new KotlinMetadataAdapter(), appView); } /** * Expands primitive array constants back to traditional primitive array * initialization code. */ private void expandPrimitiveArrayConstants() { appView.programClassPool.classesAccept(new PrimitiveArrayConstantReplacer()); } /** * Sets that target versions of the program classes. */ private void target() throws Exception { passRunner.run(new Targeter(configuration), appView); } /** * Performs the preverification step. */ private void preverify() throws Exception { // Perform the actual preverification. passRunner.run(new Preverifier(configuration), appView); } /** * Trims the line number table attributes of all program classes. */ private void trimLineNumbers() throws Exception { passRunner.run(new LineNumberTrimmer(), appView); } /** * Sorts the elements of all program classes. */ private void sortClassElements() { appView.programClassPool.classesAccept( new ClassElementSorter( /* sortInterfaces = */ true, /* sortConstants = */ true, // Sorting members can cause problems with code such as clazz.getMethods()[1] /* sortMembers = */ false, // PGD-192: Sorting attributes can cause problems for some compilers /* sortAttributes = */ false ) ); } /** * Writes the output class files. */ private void writeOutput() throws Exception { // Write out the program class pool. passRunner.run(new OutputWriter(configuration), appView); } /** * Prints out the contents of the program classes. */ private void dump() throws Exception { passRunner.run(new Dumper(configuration), appView); } /** * Returns the implementation version from the manifest. */ public static String getVersion() { Package pack = DProtect.class.getPackage(); if (pack != null) { String version = pack.getImplementationVersion(); if (version != null) { return version; } } return "undefined"; } /** * The main method for dProtect. */ public static void main(String[] args) { if (args.length == 0) { logger.warn(VERSION); logger.warn("Usage: java dprotect.DProtect [options ...]"); System.exit(1); } // Create the default options. Configuration configuration = new Configuration(); try { // Parse the options specified in the command line arguments. try (ConfigurationParser parser = new ConfigurationParser(args, System.getProperties())) { parser.parse(configuration); } // Execute ProGuard with these options. new DProtect(configuration).execute(); } catch (Exception ex) { logger.error("Unexpected error", ex); System.exit(1); } System.exit(0); } } ================================================ FILE: base/src/main/java/dprotect/ObfuscationClassSpecification.java ================================================ package dprotect; import proguard.ClassSpecification; public class ObfuscationClassSpecification extends ClassSpecification { public static enum Level { NONE, LOW, MEDIUM, HIGH }; public final Level obfuscationLvl; public ObfuscationClassSpecification(ClassSpecification classSpecification, Level level) { super(classSpecification); this.obfuscationLvl = level; } public ObfuscationClassSpecification(ClassSpecification classSpecification) { this(classSpecification, Level.NONE); } @Override public boolean equals(Object object) { if (object == null || this.getClass() != object.getClass()) { return false; } ObfuscationClassSpecification other = (ObfuscationClassSpecification)object; return this.obfuscationLvl == other.obfuscationLvl && super.equals(other); } @Override public int hashCode() { return obfuscationLvl.hashCode() ^ super.hashCode(); } @Override public Object clone() { return super.clone(); } } ================================================ FILE: base/src/main/java/dprotect/deobfuscation/Deobfuscator.java ================================================ package dprotect.deobfuscation; import dprotect.Configuration; import dprotect.obfuscation.arithmetic.*; import dprotect.obfuscation.constants.*; import dprotect.obfuscation.controlflow.*; import dprotect.obfuscation.info.ObfuscationInfo; import dprotect.obfuscation.strings.*; import java.io.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.classfile.*; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.instruction.visitor.AllInstructionVisitor; import proguard.classfile.util.PrimitiveArrayConstantReplacer; import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.visitor.*; import proguard.classfile.constant.visitor.AllConstantVisitor; import proguard.io.ExtraDataEntryNameMap; import proguard.obfuscate.util.InstructionSequenceObfuscator; import proguard.pass.Pass; import proguard.util.*; public class Deobfuscator implements Pass { private static final Logger logger = LogManager.getLogger(Deobfuscator.class); private static final String DEOBFUSCATION_STRING = "deobfuscation/string"; private final Configuration configuration; private boolean codeObfuscationString; private boolean codeObfuscationArithmeticMba; private boolean codeObfuscationConstants; private boolean codeObfuscationControlFlow; public Deobfuscator(Configuration configuration) { this.configuration = configuration; } /** * Performs obfuscation of the given program class pool. */ @Override public void execute(AppView appView) throws IOException { // Create a matcher for filtering optimizations. StringMatcher filter = configuration.optimizations != null ? new ListParser(new NameParser()).parse(configuration.obfuscations) : new ConstantMatcher(true); //codeObfuscationString = filter.matches(OBFUSCATION_STRING); //codeObfuscationArithmeticMba = filter.matches(OBFUSCATION_ARITHMETIC_MBA); //codeObfuscationConstants = filter.matches(OBFUSCATION_CONSTANTS); //codeObfuscationControlFlow = filter.matches(OBFUSCATION_CONTROL_FLOW); //logger.info("Applying code obfuscation ..."); //if (configuration.seed == null) //{ // configuration.seed = (int)System.currentTimeMillis(); //} //logger.info("Using obfuscation seed: {}", configuration.seed); //obfuscate(configuration, // appView.programClassPool, // appView.libraryClassPool, // appView.extraDataEntryNameMap); } private void deobfuscate(Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool, ExtraDataEntryNameMap extraDataEntryNameMap) throws IOException { } } ================================================ FILE: base/src/main/java/dprotect/deobfuscation/strings/XoredStrings.java ================================================ package dprotect.deobfuscation.strings; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.ClassSpecification; import proguard.MemberSpecification; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.*; import java.util.Base64; public class XoredStrings implements AttributeVisitor, InstructionVisitor, ConstantVisitor { private final byte[] KEY; private final ClassSpecification decodeSpecifier; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); private ConstantPoolEditor constantPoolEditor = null; private String stackedString = null; private LdcInfo ldcString = null; private static final Logger logger = LogManager.getLogger(XoredStrings.class); private class LdcInfo { public int offset; public byte opcode; LdcInfo(int offset, byte opcode) { this.offset = offset; this.opcode = opcode; } } public XoredStrings(String talsecKey, ClassSpecification decodeInfo) { decodeSpecifier = decodeInfo; KEY = Base64.getDecoder().decode(talsecKey); } private boolean isDecodeMethod(String clazz, String method) { if (!decodeSpecifier.className.equals(clazz)) { return false; } if (decodeSpecifier.methodSpecifications.isEmpty()) { return false; } Object spec = decodeSpecifier.methodSpecifications.get(0); if (!(spec instanceof MemberSpecification)) { return false; } MemberSpecification memberSpec = (MemberSpecification)spec; return memberSpec.name.equals(method); } public static byte[] hex2bytes(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16) ); } return data; } private String decode(byte[] bytes) { byte[] decoded = new byte[bytes.length]; for (int i = 0; i < decoded.length; ++i) { decoded[i] = (byte)((bytes[i]) ^ KEY[i % KEY.length]); } return new String(decoded); } private String decode(String enc) { return decode(hex2bytes(enc)); } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { this.constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); codeAttributeEditor.reset(codeAttribute.u4codeLength); codeAttribute.instructionsAccept(clazz, method, this); codeAttribute.accept(clazz, method, codeAttributeEditor); } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { byte opcode = constantInstruction.opcode; if (opcode == Instruction.OP_LDC || opcode == Instruction.OP_LDC_W) { this.ldcString = new LdcInfo(offset, opcode); clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } if (opcode == Instruction.OP_INVOKESTATIC) { ProgramClass programClass = (ProgramClass) clazz; Constant ref = programClass.getConstant(constantInstruction.constantIndex); if (!(ref instanceof MethodrefConstant)) { return; } MethodrefConstant methodRef = (MethodrefConstant)ref; String className = methodRef.getClassName(clazz); String methodName = methodRef.getName(clazz); if (isDecodeMethod(className, methodName)) { if (this.stackedString != null) { //logger.info("{} -> {}", stackedString, decode(stackedString)); this.codeAttributeEditor.deleteInstruction(offset); int idx = this.constantPoolEditor.addStringConstant(decode(this.stackedString)); ConstantInstruction replacedLdc = new ConstantInstruction(this.ldcString.opcode, idx); this.codeAttributeEditor.replaceInstruction(this.ldcString.offset, replacedLdc); this.stackedString = null; } } } } // Implementations for ConstantVisitor. @Override public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { clazz.constantPoolEntryAccept(stringConstant.u2stringIndex, this); } @Override public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { this.stackedString = utf8Constant.getString(); } @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} } ================================================ FILE: base/src/main/java/dprotect/obfuscation/CodeObfuscator.java ================================================ package dprotect.obfuscation; import dprotect.Configuration; import dprotect.obfuscation.arithmetic.*; import dprotect.obfuscation.constants.*; import dprotect.obfuscation.controlflow.*; import dprotect.obfuscation.info.ObfuscationInfo; import dprotect.obfuscation.strings.*; import java.io.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.classfile.*; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.instruction.visitor.AllInstructionVisitor; import proguard.classfile.util.PrimitiveArrayConstantReplacer; import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.visitor.*; import proguard.classfile.constant.visitor.AllConstantVisitor; import proguard.io.ExtraDataEntryNameMap; import proguard.obfuscate.util.InstructionSequenceObfuscator; import proguard.pass.Pass; import proguard.util.*; public class CodeObfuscator implements Pass { private static final Logger logger = LogManager.getLogger(CodeObfuscator.class); private static final String OBFUSCATION_STRING = "obfuscation/string"; private static final String OBFUSCATION_ARITHMETIC_MBA = "obfuscation/arithmetic/mba"; private static final String OBFUSCATION_CONSTANTS = "obfuscation/constants"; private static final String OBFUSCATION_CONTROL_FLOW = "obfuscation/controlflow"; private final Configuration configuration; private boolean codeObfuscationString; private boolean codeObfuscationArithmeticMba; private boolean codeObfuscationConstants; private boolean codeObfuscationControlFlow; public CodeObfuscator(Configuration configuration) { this.configuration = configuration; } /** * Performs obfuscation of the given program class pool. */ @Override public void execute(AppView appView) throws IOException { // Create a matcher for filtering optimizations. StringMatcher filter = configuration.optimizations != null ? new ListParser(new NameParser()).parse(configuration.obfuscations) : new ConstantMatcher(true); codeObfuscationString = filter.matches(OBFUSCATION_STRING); codeObfuscationArithmeticMba = filter.matches(OBFUSCATION_ARITHMETIC_MBA); codeObfuscationConstants = filter.matches(OBFUSCATION_CONSTANTS); codeObfuscationControlFlow = filter.matches(OBFUSCATION_CONTROL_FLOW); logger.info("Applying code obfuscation ..."); if (configuration.seed == null) { configuration.seed = (int)System.currentTimeMillis(); } logger.info("Using obfuscation seed: {}", configuration.seed); obfuscate(configuration, appView.programClassPool, appView.libraryClassPool, appView.extraDataEntryNameMap); } private void obfuscate(Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool, ExtraDataEntryNameMap extraDataEntryNameMap) throws IOException { /* Make sure that PrimitiveArrayConstant are correctly expanded before running * the pass. * * This PrimitiveArrayConstantReplacer is also run in the main DProtect.java/Proguard.java * but it comes after the optimization/obfuscation pipeline. I don't know if it is a bug * but for this pass, it needs to be run **before** */ { programClassPool.classesAccept(new PrimitiveArrayConstantReplacer()); } if (codeObfuscationString) { runStringObfuscation(configuration, programClassPool, libraryClassPool, extraDataEntryNameMap); } if (codeObfuscationControlFlow) { runControlFlowObfuscation(configuration, programClassPool, libraryClassPool, extraDataEntryNameMap); } if (codeObfuscationArithmeticMba) { runArithmeticObfuscation(configuration, programClassPool, libraryClassPool, extraDataEntryNameMap); } if (codeObfuscationConstants) { runConstantsObfuscation(configuration, programClassPool, libraryClassPool, extraDataEntryNameMap); } programClassPool.accept(new AllClassVisitor( new AllFieldVisitor( new ClassReferenceInitializer(programClassPool, libraryClassPool)))); programClassPool.accept(new AllClassVisitor( new AllConstantVisitor( new ClassReferenceInitializer(programClassPool, libraryClassPool)))); } private void runStringObfuscation(Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool, ExtraDataEntryNameMap extraDataEntryNameMap) { logger.info("dProtect: Applying strings encoding ..."); programClassPool.accept( new AllClassVisitor( new ClassVisitor() { /* * The purpose of this visitor is to early filter * on the classes that are flagged with the obfuscate-strings options */ public ClassVisitor obfuscator; @Override public void visitAnyClass(Clazz clazz) { if (ObfuscationInfo.getObfuscationInfo(clazz).encodeStrings) { /* * StringFieldMarker is used to flag the strings * that are associated with field write accesses */ clazz.accept( new AllMethodVisitor( new AllAttributeVisitor( new AllInstructionVisitor( new StringFieldMarker())))); /* * Run the obfuscation pass */ obfuscator.visitAnyClass(clazz); } } ClassVisitor apply(ClassVisitor obfuscator) { this.obfuscator = obfuscator; return this; } }.apply(new StringObfuscator(configuration.obfuscateStringsList, configuration.seed)))); } private void runArithmeticObfuscation(Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool, ExtraDataEntryNameMap extraDataEntryNameMap) { MBANormalizer normalizer = new MBANormalizer (programClassPool, libraryClassPool); MBAObfuscationAdd mbaAdd = new MBAObfuscationAdd(programClassPool, libraryClassPool); MBAObfuscationXor mbaXor = new MBAObfuscationXor(programClassPool, libraryClassPool); MBAObfuscationAnd mbaAnd = new MBAObfuscationAnd(programClassPool, libraryClassPool); MBAObfuscationOr mbaOr = new MBAObfuscationOr (programClassPool, libraryClassPool); MBAObfuscationSub mbaSub = new MBAObfuscationSub(programClassPool, libraryClassPool); logger.info("dProtect: Applying Mixed Boolean-Arithmetic expressions ..."); programClassPool.accept( new AllClassVisitor( new ClassVisitor() { /* * Early filter * on the classes that are flagged with obfuscate-arithmetic */ public MemberVisitor obfuscator; @Override public void visitAnyClass(Clazz clazz) { if (ObfuscationInfo.getObfuscationInfo(clazz).arithmetic != null) { clazz.methodsAccept(new ArithmeticObfuscationFilter(obfuscator)); } } ClassVisitor apply(MemberVisitor obfuscator) { this.obfuscator = obfuscator; return this; } }.apply(new MultiMemberVisitor( new InstructionSequenceObfuscator(normalizer), new InstructionSequenceObfuscator(mbaAdd), new InstructionSequenceObfuscator(mbaXor), new InstructionSequenceObfuscator(mbaAnd), new InstructionSequenceObfuscator(mbaOr), new InstructionSequenceObfuscator(mbaSub))))); } private void runControlFlowObfuscation(Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool, ExtraDataEntryNameMap extraDataEntryNameMap) { logger.info("dProtect: Obfuscating control-flow ..."); programClassPool.accept( new AllClassVisitor( new ClassVisitor() { /* * Early filter classes that are flagged with 'obfuscate-controlflow' */ public ClassVisitor obfuscator; @Override public void visitAnyClass(Clazz clazz) { if (ObfuscationInfo.getObfuscationInfo(clazz).controlflow != null) { /* * Run the obfuscation pass */ obfuscator.visitAnyClass(clazz); } } ClassVisitor apply(ClassVisitor obfuscator) { this.obfuscator = obfuscator; return this; } }.apply(new ControlFlowObfuscation(configuration.seed)))); } private void runConstantsObfuscation(Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool, ExtraDataEntryNameMap extraDataEntryNameMap) { logger.info("dProtect: Protecting Constants ..."); programClassPool.accept( new AllClassVisitor( new ClassVisitor() { /* * Early filter classes that are flagged with 'obfuscate-constants' */ public ClassVisitor obfuscator; @Override public void visitAnyClass(Clazz clazz) { if (ObfuscationInfo.getObfuscationInfo(clazz).constants != null) { /* * Flag constant associated with marked fields * /!\ TODO(romain): To be implemented /!\ */ //clazz.accept( // new AllMethodVisitor( // new AllAttributeVisitor( // new AllInstructionVisitor( // new ConstantFieldMarker())))); /* * Run the obfuscation pass */ obfuscator.visitAnyClass(clazz); } } ClassVisitor apply(ClassVisitor obfuscator) { this.obfuscator = obfuscator; return this; } }.apply(new ConstantsObfuscator(configuration.seed)))); } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/Marker.java ================================================ package dprotect.obfuscation; import dprotect.ClassObfuSpecVisitorFactory; import dprotect.Configuration; import dprotect.obfuscation.info.ObfuscationInfoSetter; import dprotect.obfuscation.strings.StringObfuscationMarker; import dprotect.obfuscation.arithmetic.ArithmeticObfuscationMarker; import dprotect.obfuscation.constants.ConstantObfuscationMarker; import dprotect.obfuscation.controlflow.ControlFlowObfuscationMarker; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.classfile.visitor.*; import proguard.pass.Pass; import proguard.ClassSpecificationVisitorFactory; public class Marker implements Pass { private static final Logger logger = LogManager.getLogger(Marker.class); private final Configuration configuration; public Marker(Configuration configuration) { this.configuration = configuration; } @Override public void execute(AppView appView) { logger.info("Marking classes and class members to be dprotect-obfuscated ..."); // Attach obfuscation info to all the classes and class member ObfuscationInfoSetter obfSetter = new ObfuscationInfoSetter(); appView.programClassPool.classesAccept(obfSetter); appView.programClassPool.classesAccept(new AllMemberVisitor(obfSetter)); MultiClassPoolVisitor classPoolVisitor = new MultiClassPoolVisitor( createStringObfuscationMarker(configuration), createCFObfuscationMarker(configuration), createArithmeticObfuscationMarker(configuration), createConstantsObfuscationMarker(configuration) ); appView.programClassPool.accept(classPoolVisitor); appView.libraryClassPool.accept(classPoolVisitor); } // Marker factory private ClassPoolVisitor createStringObfuscationMarker(Configuration configuration) { StringObfuscationMarker marker = new StringObfuscationMarker(); return new ClassSpecificationVisitorFactory() .createClassPoolVisitor(configuration.obfuscateStrings, marker, marker); } private ClassPoolVisitor createArithmeticObfuscationMarker(Configuration configuration) { return new ClassObfuSpecVisitorFactory() .createClassPoolVisitor(configuration.obfuscateArithmetic, /* Class Visitor */ (spec) -> { return new ArithmeticObfuscationMarker(spec); }, /* Member Visitor */ (spec) -> { return new ArithmeticObfuscationMarker(spec); }); } private ClassPoolVisitor createConstantsObfuscationMarker(Configuration configuration) { return new ClassObfuSpecVisitorFactory() .createClassPoolVisitor(configuration.obfuscateConstants, /* Class Visitor */ (spec) -> { return new ConstantObfuscationMarker(spec); }, /* Member Visitor */ (spec) -> { return new ConstantObfuscationMarker(spec); }); } private ClassPoolVisitor createCFObfuscationMarker(Configuration configuration) { return new ClassObfuSpecVisitorFactory() .createClassPoolVisitor(configuration.obfuscateControlFlow, /* Class Visitor */ (spec) -> { return new ControlFlowObfuscationMarker(spec); }, /* Member Visitor */ (spec) -> { return new ControlFlowObfuscationMarker(spec); }); } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/arithmetic/ArithmeticObfuscationFilter.java ================================================ package dprotect.obfuscation.arithmetic; import dprotect.obfuscation.info.ObfuscationInfo; import static dprotect.ObfuscationClassSpecification.Level; import proguard.classfile.visitor.MemberVisitor; import proguard.classfile.Clazz; import proguard.classfile.Member; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramMethod; public class ArithmeticObfuscationFilter implements MemberVisitor { static final int NONE_OR_LOW_ROUNDS = 1; static final int MEDIUM_ROUNDS = 2; static final int HIGH_ROUNDS = 3; private final MemberVisitor visitor; public ArithmeticObfuscationFilter(MemberVisitor visitor) { this.visitor = visitor; } @Override public void visitAnyMember(Clazz clazz, Member member) { } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(programMethod); if (info == null || info.arithmetic == null) { return; } Level level = info.arithmetic.level; int passes = level == Level.NONE ? NONE_OR_LOW_ROUNDS : level == Level.LOW ? NONE_OR_LOW_ROUNDS : level == Level.MEDIUM ? MEDIUM_ROUNDS : level == Level.HIGH ? HIGH_ROUNDS : 0; for (int i = 0; i < passes; ++i) { visitor.visitProgramMethod(programClass, programMethod); } } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/arithmetic/ArithmeticObfuscationInfo.java ================================================ package dprotect.obfuscation.arithmetic; import static dprotect.ObfuscationClassSpecification.Level; public class ArithmeticObfuscationInfo { public Level level = Level.NONE; public boolean skipFloat = false; public ArithmeticObfuscationInfo(Level level) { this.level = level; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/arithmetic/ArithmeticObfuscationMarker.java ================================================ package dprotect.obfuscation.arithmetic; import dprotect.ArithmeticObfuscationClassSpecification; import dprotect.ObfuscationClassSpecification; import dprotect.obfuscation.info.ObfuscationInfo; import proguard.classfile.visitor.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; public class ArithmeticObfuscationMarker implements ClassVisitor, MemberVisitor { private static final Logger logger = LogManager.getLogger(ArithmeticObfuscationMarker.class); private final ArithmeticObfuscationClassSpecification spec; public ArithmeticObfuscationMarker(ObfuscationClassSpecification spec) { this.spec = (ArithmeticObfuscationClassSpecification)spec; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(programClass); if (info.arithmetic == null) { info.arithmetic = new ArithmeticObfuscationInfo(spec.obfuscationLvl); } } // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) { } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(programMethod); if (info.arithmetic == null) { info.arithmetic = new ArithmeticObfuscationInfo(spec.obfuscationLvl); } info.arithmetic.skipFloat = spec.skipFloat; } @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/arithmetic/MBANormalizer.java ================================================ package dprotect.obfuscation.arithmetic; import proguard.classfile.constant.*; import proguard.classfile.instruction.*; import proguard.classfile.editor.*; import proguard.classfile.util.InstructionSequenceMatcher; import proguard.classfile.*; import proguard.obfuscate.util.ReplacementSequences; public class MBANormalizer implements ReplacementSequences { private static final int A = InstructionSequenceMatcher.A; private static final int B = InstructionSequenceMatcher.B; private static final int X = InstructionSequenceMatcher.X; private static final int Y = InstructionSequenceMatcher.Y; private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; public MBANormalizer(ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); SEQUENCES = new Instruction[][][] { { ____.aload(A) .iload(X) .dup2() .__(), ____.aload(A) .iload(X) .nop() // See: https://github.com/Guardsquare/proguard-core/issues/75 .aload(A) .iload(X) .__() }, { ____.aload(A) .bipush(X) .dup2() .__(), ____.aload(A) .bipush(X) .nop() // See: https://github.com/Guardsquare/proguard-core/issues/75 .aload(A) .bipush(X) .__() }, //Fix for: https://github.com/Guardsquare/proguard-core/issues/75 { ____.aload(A) .iload(X) .aload(A) .iload(X) .__(), ____.aload(A) .iload(X) .nop() .aload(A) .iload(X) .__() }, }; CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/arithmetic/MBAObfuscationAdd.java ================================================ package dprotect.obfuscation.arithmetic; import proguard.classfile.constant.*; import proguard.classfile.instruction.*; import proguard.classfile.editor.*; import proguard.classfile.util.InstructionSequenceMatcher; import proguard.classfile.*; import proguard.obfuscate.util.ReplacementSequences; // X + Y <==> (X & Y) + (X | Y) public class MBAObfuscationAdd implements ReplacementSequences { private static final int A = InstructionSequenceMatcher.A; private static final int B = InstructionSequenceMatcher.B; private static final int X = InstructionSequenceMatcher.X; private static final int Y = InstructionSequenceMatcher.Y; private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; public MBAObfuscationAdd(ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); /* * These transformations are taken from https://github.com/quarkslab/sspam * developed by Ninon Eyrolles */ SEQUENCES = new Instruction[][][] { /* * Integer Support * =================================================== */ // (int) X + Y { ____.iload(X) .iload(Y) .iadd() .__(), ____.iload(X) .iload(Y) .iand() .iload(X) .iload(Y) .ior() .iadd() .__(), }, // (int) X + CST { ____.iload(X) .iconst(Y) .iadd() .__(), ____.iload(X) .iconst(Y) .iand() .iload(X) .iconst(Y) .ior() .iadd() .__(), }, // (int) X + LARGE_CST { ____.iload(X) .ldc_(Y) .iadd() .__(), ____.iload(X) .ldc_(Y) .iand() .iload(X) .ldc_(Y) .ior() .iadd() .__(), }, // (int) X + LARGE_CST { ____.iload(X) .ldc_w_(Y) .iadd() .__(), ____.iload(X) .ldc_w_(Y) .iand() .iload(X) .ldc_w_(Y) .ior() .iadd() .__(), }, // LARGE_CST + Y { ____.bipush(X) .iload(Y) .iadd() .__(), ____.bipush(X) .iload(Y) .iand() .bipush(X) .iload(Y) .ior() .iadd() .__(), }, // (int) X++ { ____.iinc(X, Y) .__(), ____.iload(X) .bipush(Y) .iand() .iload(X) .bipush(Y) .ior() .iadd() .istore(X) .__(), }, /* * Long Support * =================================================== */ // (long) X + Y { ____.lload(X) .lload(Y) .ladd() .__(), ____.lload(X) .lload(Y) .land() .lload(X) .lload(Y) .lor() .ladd() .__(), }, // (long) X + CST { ____.lload(X) .lconst(Y) .ladd() .__(), ____.lload(X) .lconst(Y) .land() .lload(X) .lconst(Y) .lor() .ladd() .__(), }, // (long) X + LARGE_CST { ____.lload(X) .ldc2_w(Y) .ladd() .__(), ____.lload(X) .ldc2_w(Y) .land() .lload(X) .ldc2_w(Y) .lor() .ladd() .__(), }, /* * Long Array Support * /!\ TODO: To be completed /!\ * =================================================== */ // A[X] + B[Y] (iconst / iconst for the indexes) { ____ // Op 0 -> A[X] .aload(A) .iconst(X) .laload() // Op 1 -> B[Y] .aload(B) .iconst(Y) .laload() // Op: (long) Add .ladd() .__(), ____ // A[X] & B[Y] { .aload(A) .iconst(X) .laload() .aload(B) .iconst(Y) .laload() .land() // } // A[X] | B[Y] { .aload(A) .iconst(X) .laload() .aload(B) .iconst(Y) .laload() .lor() // } .ladd() .__(), }, // A[X] + B[Y] (bipush / bipush for the indexes) { ____ // Op 0 -> A[X] .aload(A) .bipush(X) .laload() // Op 1 -> B[Y] .aload(B) .bipush(Y) .laload() // Op: (long) Add .ladd() .__(), ____ // A[X] & B[Y] { .aload(A) .bipush(X) .laload() .aload(B) .bipush(Y) .laload() .land() // } // A[X] | B[Y] { .aload(A) .bipush(X) .laload() .aload(B) .bipush(Y) .laload() .lor() // } .ladd() .__(), }, /* * Int Array Support * /!\ TODO: To be completed /!\ * =================================================== */ // A[X] + B[Y] { ____ // A[X] .aload(A) .iload(X) .iaload() // B[Y] .aload(B) .iload(Y) .iaload() // A[X] + B[Y] .iadd() .__(), ____ // A[X] & A[Y] { .aload(A) .iload(X) .iaload() .aload(B) .iload(Y) .iaload() .iand() // } // A[X] | A[Y] { .aload(A) .iload(X) .iaload() .aload(B) .iload(Y) .iaload() .ior() // } .iadd() .__(), }, /* * Float Support * /!\ TODO /!\ * =================================================== */ // .... /* * Double Support * /!\ TODO /!\ * =================================================== */ }; CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/arithmetic/MBAObfuscationAnd.java ================================================ package dprotect.obfuscation.arithmetic; import proguard.classfile.constant.*; import proguard.classfile.instruction.*; import proguard.classfile.editor.*; import proguard.classfile.util.InstructionSequenceMatcher; import proguard.classfile.*; import proguard.obfuscate.util.ReplacementSequences; // A & B = (X + Y) - (X | Y) public class MBAObfuscationAnd implements ReplacementSequences { private static final int X = InstructionSequenceMatcher.X; private static final int Y = InstructionSequenceMatcher.Y; private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; public MBAObfuscationAnd(ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); SEQUENCES = new Instruction[][][] { /* * Integer Support * =================================================== */ // X & Y { ____.iload(X) .iload(Y) .iand() .__(), ____.iload(X) .iload(Y) .iadd() .iload(X) .iload(Y) .ior() .isub() .__(), }, // X & CST { ____.iload(X) .iconst(Y) .iand() .__(), ____.iload(X) .iconst(Y) .iadd() .iload(X) .iconst(Y) .ior() .isub() .__(), }, // X & LARGE_CST { ____.iload(X) .ldc_(Y) .iand() .__(), ____.iload(X) .ldc_(Y) .iadd() .iload(X) .ldc_(Y) .ior() .isub() .__(), }, // X & LARGE_CST { ____.iload(X) .ldc_w_(Y) .iand() .__(), ____.iload(X) .ldc_w_(Y) .iadd() .iload(X) .ldc_w_(Y) .ior() .isub() .__(), }, /* * Long Support * =================================================== */ // X & Y { ____.lload(X) .lload(Y) .land() .__(), ____.lload(X) .lload(Y) .ladd() .lload(X) .lload(Y) .lor() .lsub() .__(), }, // X & CST { ____.lload(X) .lconst(Y) .land() .__(), ____.lload(X) .lconst(Y) .ladd() .lload(X) .lconst(Y) .lor() .lsub() .__(), }, // X & LARGE_CST { ____.lload(X) .ldc2_w(Y) .land() .__(), ____.lload(X) .ldc2_w(Y) .ladd() .lload(X) .ldc2_w(Y) .lor() .lsub() .__(), }, }; // TODO(xxx): Handle Float and Double CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/arithmetic/MBAObfuscationOr.java ================================================ package dprotect.obfuscation.arithmetic; import proguard.classfile.constant.*; import proguard.classfile.instruction.*; import proguard.classfile.editor.*; import proguard.classfile.util.InstructionSequenceMatcher; import proguard.classfile.*; import proguard.obfuscate.util.ReplacementSequences; // X + Y + 1 + (~X | ~Y) public class MBAObfuscationOr implements ReplacementSequences { private static final int X = InstructionSequenceMatcher.X; private static final int Y = InstructionSequenceMatcher.Y; private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; public MBAObfuscationOr(ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); SEQUENCES = new Instruction[][][] { /* * Integer Support * =================================================== */ // X | Y { ____.iload(X) .iload(Y) .ior() .__(), ____.iload(X) .iload(Y) .iadd() .iconst_1() .iadd() .iload(X) .iconst_m1() .ixor() .iload(Y) .iconst_m1() .ixor() .ior() .iadd() .__(), }, // X | CST { ____.iload(X) .iconst(Y) .ior() .__(), ____.iload(X) .iconst(Y) .iadd() .iconst_1() .iadd() .iload(X) .iconst_m1() .ixor() .iconst(Y) .iconst_m1() .ixor() .ior() .iadd() .__(), }, // X | LARGE_CST { ____.iload(X) .ldc_(Y) .ior() .__(), ____.iload(X) .ldc_(Y) .iadd() .iconst_1() .iadd() .iload(X) .iconst_m1() .ixor() .ldc_(Y) .iconst_m1() .ixor() .ior() .iadd() .__(), }, // X | LARGE_CST { ____.iload(X) .ldc_w_(Y) .ior() .__(), ____.iload(X) .ldc_w_(Y) .iadd() .iconst_1() .iadd() .iload(X) .iconst_m1() .ixor() .ldc_w_(Y) .iconst_m1() .ixor() .ior() .iadd() .__(), }, /* * Long Support * =================================================== */ // X | Y { ____.lload(X) .lload(Y) .lor() .__(), ____.lload(X) .lload(Y) .ladd() .lconst_1() .ladd() .lload(X) .ldc2_w((long)-1) .lxor() .lload(Y) .ldc2_w((long)-1) .lxor() .lor() .ladd() .__(), }, // X | CST { ____.lload(X) .lconst(Y) .lor() .__(), ____.lload(X) .lconst(Y) .ladd() .lconst_1() .ladd() .lload(X) .ldc2_w((long)-1) .lxor() .lconst(Y) .ldc2_w((long)-1) .lxor() .lor() .ladd() .__(), }, // X | CST { ____.lload(X) .ldc2_w(Y) .lor() .__(), ____.lload(X) .ldc2_w(Y) .ladd() .lconst_1() .ladd() .lload(X) .ldc2_w((long)-1) .lxor() .ldc2_w(Y) .ldc2_w((long)-1) .lxor() .lor() .ladd() .__(), }, }; // TODO(XXX): Handle Float and Double CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/arithmetic/MBAObfuscationSub.java ================================================ package dprotect.obfuscation.arithmetic; import proguard.classfile.constant.*; import proguard.classfile.instruction.*; import proguard.classfile.editor.*; import proguard.classfile.util.InstructionSequenceMatcher; import proguard.classfile.*; import proguard.obfuscate.util.ReplacementSequences; public class MBAObfuscationSub implements ReplacementSequences { private static final int X = InstructionSequenceMatcher.X; private static final int Y = InstructionSequenceMatcher.Y; private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; public MBAObfuscationSub(ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); // X - Y <=> (X ^ -Y) + 2*(X & -Y) SEQUENCES = new Instruction[][][] { /* * Integer Support * =================================================== */ // (int) A - B { ____.iload(X) .iload(Y) .isub() .__(), ____.iload(X) .iload(Y) .ineg() .ixor() .iconst_2() .iload(X) .iload(Y) .ineg() .iand() .imul() .iadd() .__(), }, // (int) A - CST { ____.iload(X) .iconst(Y) .isub() .__(), ____.iload(X) .iconst(Y) .ineg() .ixor() .iconst_2() .iload(X) .iconst(Y) .ineg() .iand() .imul() .iadd() .__(), }, // (int) A - LARGE_CST { ____.iload(X) .ldc_(Y) .isub() .__(), ____.iload(X) .ldc_(Y) .ineg() .ixor() .iconst_2() .iload(X) .ldc_(Y) .ineg() .iand() .imul() .iadd() .__(), }, // (int) A - LARGE_CST { ____.iload(X) .ldc_w_(Y) .isub() .__(), ____.iload(X) .ldc_w_(Y) .ineg() .ixor() .iconst_2() .iload(X) .ldc_w_(Y) .ineg() .iand() .imul() .iadd() .__(), }, /* * Long Support * =================================================== */ // (long) A - B { ____.lload(X) .lload(Y) .lsub() .__(), ____.lload(X) .lload(Y) .lneg() .lxor() .ldc2_w((long)2) .lload(X) .lload(Y) .lneg() .land() .lmul() .ladd() .__(), }, // (long) A - CST { ____.lload(X) .lconst(Y) .lsub() .__(), ____.lload(X) .lconst(Y) .lneg() .lxor() .ldc2_w((long)2) .lload(X) .lconst(Y) .lneg() .land() .lmul() .ladd() .__(), }, // (long) A - LARGE_CST { ____.lload(X) .ldc2_w(Y) .lsub() .__(), ____.lload(X) .ldc2_w(Y) .lneg() .lxor() .ldc2_w((long)2) .lload(X) .ldc2_w(Y) .lneg() .land() .lmul() .ladd() .__(), }, }; // TODO(xxx): Handle Float and Double CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/arithmetic/MBAObfuscationXor.java ================================================ package dprotect.obfuscation.arithmetic; import proguard.classfile.constant.*; import proguard.classfile.instruction.*; import proguard.classfile.editor.*; import proguard.classfile.util.InstructionSequenceMatcher; import proguard.classfile.*; import proguard.obfuscate.util.ReplacementSequences; // A ^ B <=> (A | B) - (A & B) public class MBAObfuscationXor implements ReplacementSequences { private static final int A = InstructionSequenceMatcher.A; private static final int B = InstructionSequenceMatcher.B; private static final int X = InstructionSequenceMatcher.X; private static final int Y = InstructionSequenceMatcher.Y; private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; public MBAObfuscationXor(ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); SEQUENCES = new Instruction[][][] { /* * Integer Support * =================================================== */ // (int) X ^ Y { ____.iload(X) .iload(Y) .ixor() .__(), ____.iload(X) .iload(Y) .ior() .iload(X) .iload(Y) .iand() .isub() .__() }, // (int) X ^ CST { ____.iload(X) .iconst(Y) .ixor() .__(), ____.iload(X) .iconst(Y) .ior() .iload(X) .iconst(Y) .iand() .isub() .__() }, // (int) X ^ LARGE_CST { ____.iload(X) .ldc_(Y) .ixor() .__(), ____.iload(X) .ldc_(Y) .ior() .iload(X) .ldc_(Y) .iand() .isub() .__() }, // (int) X ^ LARGE_CST { ____.iload(X) .ldc_w_(Y) .ixor() .__(), ____.iload(X) .ldc_w_(Y) .ior() .iload(X) .ldc_w_(Y) .iand() .isub() .__() }, /* * Long Support * =================================================== */ // (long) X ^ B { ____.lload(X) .lload(Y) .lxor() .__(), ____.lload(X) .lload(Y) .lor() .lload(X) .lload(Y) .land() .lsub() .__() }, // (long) X ^ CST { ____.lload(X) .lconst(Y) .lxor() .__(), ____.lload(X) .lconst(Y) .lor() .lload(X) .lconst(Y) .land() .lsub() .__() }, // (long) X ^ LARGE_CST { ____.lload(X) .ldc2_w(Y) .lxor() .__(), ____.lload(X) .ldc2_w(Y) .lor() .lload(X) .ldc2_w(Y) .land() .lsub() .__() }, /* * Int Array Support * /!\ NOT FINISHED /!\ * =================================================== */ // A[X] ^ B[Y] { ____ // A[X] .aload(A) .iload(X) .iaload() // B[Y] .aload(B) .iload(Y) .iaload() // A[X] ^ B[Y] .ixor() .__(), ____ // A[X] | A[Y] { .aload(A) .iload(X) .iaload() .aload(B) .iload(Y) .iaload() .ior() // } // A[X] & A[Y] { .aload(A) .iload(X) .iaload() .aload(B) .iload(Y) .iaload() .iand() // } .isub() .__(), }, }; CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/constants/ConstantFieldMarker.java ================================================ package dprotect.obfuscation.constants; import dprotect.obfuscation.info.ObfuscationInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.visitor.*; import proguard.classfile.instruction.*; import proguard.classfile.attribute.CodeAttribute; // TODO(romain): Not yet implemented but it aims at marking constants // associated with user-flagged fields (like for the strings) public class ConstantFieldMarker implements InstructionVisitor, ConstantVisitor { private static final Logger logger = LogManager.getLogger(ConstantFieldMarker.class); // Implementations for MemberVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { } @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { } // Implementations for ConstantVisitor. @Override public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { } @Override public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { } @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} } ================================================ FILE: base/src/main/java/dprotect/obfuscation/constants/ConstantObfuscationInfo.java ================================================ package dprotect.obfuscation.constants; import static dprotect.ObfuscationClassSpecification.Level; public class ConstantObfuscationInfo { public Level level = Level.NONE; public ConstantObfuscationInfo(Level level) { this.level = level; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/constants/ConstantObfuscationMarker.java ================================================ package dprotect.obfuscation.constants; import dprotect.ConstantObfuscationClassSpecification; import dprotect.ObfuscationClassSpecification; import dprotect.obfuscation.info.ObfuscationInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.*; public class ConstantObfuscationMarker implements ClassVisitor, MemberVisitor { private static final Logger logger = LogManager.getLogger(ConstantObfuscationMarker.class); private final ConstantObfuscationClassSpecification spec; public ConstantObfuscationMarker(ObfuscationClassSpecification spec) { this.spec = (ConstantObfuscationClassSpecification)spec; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Flag the class as it enables to quickly determine whether // it should be considered by the obfuscation pass ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(programClass); if (info.constants == null) { info.constants = new ConstantObfuscationInfo(spec.obfuscationLvl); } } // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) { } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(programMethod); if (info.constants == null) { info.constants = new ConstantObfuscationInfo(spec.obfuscationLvl); } } @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(programField); if (info.constants == null) { info.constants = new ConstantObfuscationInfo(spec.obfuscationLvl); } } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/constants/ConstantsObfuscator.java ================================================ package dprotect.obfuscation.constants; import java.util.ArrayList; import java.util.HashMap; import java.util.Random; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.Constant; import proguard.classfile.constant.visitor.*; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.*; import proguard.classfile.visitor.*; public class ConstantsObfuscator implements ClassVisitor, AttributeVisitor, InstructionVisitor, ConstantVisitor { private static final Logger logger = LogManager.getLogger(ConstantsObfuscator.class); private static final String OPAQUE_CONSTANTS_ARRAY_PREFIX = "OPAQUE_CONSTANTS_ARRAY"; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); private ProgramField arrayField = null; private Long longVal = null; private ArrayList constants = new ArrayList(); private HashMap constantsKeys = new HashMap(); private final Random rand; public ConstantsObfuscator(int seed) { rand = new Random((long)seed); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { if (clazz instanceof ProgramClass) { visitProgramClass((ProgramClass)clazz); } } @Override public void visitProgramClass(ProgramClass programClass) { if ((programClass.getAccessFlags() & AccessConstants.INTERFACE) != 0 || (programClass.getAccessFlags() & AccessConstants.ABSTRACT) != 0) { return; } constants.clear(); constantsKeys.clear(); ClassBuilder classBuilder = new ClassBuilder(programClass); arrayField = classBuilder.addAndReturnField(AccessConstants.PRIVATE | AccessConstants.STATIC, OPAQUE_CONSTANTS_ARRAY_PREFIX, "[J"); programClass.accept(new AllMethodVisitor( new AllAttributeVisitor( this))); if (!constants.isEmpty()) { new InitializerEditor(programClass).addStaticInitializerInstructions(/*mergeIntoExistingInitializer=*/true, ____ -> { // Allocate space for the array that holds the constants ____.ldc(constants.size()) .newarray(Instruction.ARRAY_T_LONG) .putstatic(programClass, arrayField); // Push the values for (int i = 0; i < constants.size(); ++i) { Long value = constants.get(i); Long key = constantsKeys.get(value); Long encoded = value ^ key; ____.getstatic(programClass, arrayField) .sipush(i) .ldc2_w(encoded) .lastore(); } }); } } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttributeEditor.reset(codeAttribute.u4codeLength); codeAttribute.instructionsAccept(clazz, method, this); codeAttribute.accept(clazz, method, codeAttributeEditor); } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction instruction) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz); byte opcode = instruction.opcode; switch (opcode) { case Instruction.OP_BIPUSH: { int value = instruction.constant; int index = getOrInsert((long)value); Long key = constantsKeys.get(Long.valueOf(value)); codeAttributeEditor.replaceInstruction(offset, ____.getstatic(clazz, arrayField) .ldc(index) .laload() .l2i() .ldc(key.intValue()) .ixor() .__()); break; } case Instruction.OP_ICONST_0: // 0 value { int value = instruction.constant; int index = getOrInsert((long)value); Long key = constantsKeys.get(Long.valueOf(value)); codeAttributeEditor.replaceInstruction(offset, ____.getstatic(clazz, arrayField) .ldc(index) .laload() .l2i() .ldc(key.intValue()) .ixor() .__()); break; } case Instruction.OP_ICONST_1: // 1 value { int value = instruction.constant; int index = getOrInsert((long)value); Long key = constantsKeys.get(Long.valueOf(value)); codeAttributeEditor.replaceInstruction(offset, ____.getstatic(clazz, arrayField) .ldc(index) .laload() .l2i() .ldc(key.intValue()) .ixor() .__()); break; } case Instruction.OP_ICONST_2: // 2 value { int value = instruction.constant; int index = getOrInsert((long)value); Long key = constantsKeys.get(Long.valueOf(value)); codeAttributeEditor.replaceInstruction(offset, ____.getstatic(clazz, arrayField) .ldc(index) .laload() .l2i() .ldc(key.intValue()) .ixor() .__()); break; } case Instruction.OP_ICONST_3: // 3 value { int value = instruction.constant; int index = getOrInsert((long)value); Long key = constantsKeys.get(Long.valueOf(value)); codeAttributeEditor.replaceInstruction(offset, ____.getstatic(clazz, arrayField) .ldc(index) .laload() .l2i() .ldc(key.intValue()) .ixor() .__()); break; } } } @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { longVal = null; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); if (longVal == null) { return; } int index = getOrInsert(longVal); Long key = constantsKeys.get(longVal); InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz); switch(constantInstruction.opcode) { case Instruction.OP_LDC: { codeAttributeEditor.replaceInstruction(offset, ____.getstatic(clazz, arrayField) .ldc(index) .laload() .l2i() .ldc(key.intValue()) .ixor() .__()); break; } case Instruction.OP_LDC_W: { codeAttributeEditor.replaceInstruction(offset, ____.getstatic(clazz, arrayField) .ldc(index) .laload() .l2i() .ldc(key.intValue()) .ixor() .__()); break; } case Instruction.OP_LDC2_W: { codeAttributeEditor.replaceInstruction(offset, ____.getstatic(clazz, arrayField) .ldc(index) .laload() .ldc2_w(key.longValue()) .lxor() .__()); break; } } } // Implementations for ConstantVisitor. @Override public void visitIntegerConstant(Clazz clazz, IntegerConstant constant) { longVal = (long)constant.getValue(); } @Override public void visitLongConstant(Clazz clazz, LongConstant constant) { longVal = constant.getValue(); } @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} // Helpers int getOrInsert(Long value) { int index = constants.indexOf(value); if (index == -1) { index = constants.size(); constants.add(value); int key = rand.nextInt(Integer.MAX_VALUE - 1); constantsKeys.put(value, (long)key); } return index; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/controlflow/ControlFlowObfuscation.java ================================================ package dprotect.obfuscation.controlflow; import java.util.Random; import java.util.ArrayList; import java.util.HashMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.*; import proguard.classfile.visitor.*; import proguard.evaluation.*; import proguard.evaluation.value.*; public class ControlFlowObfuscation implements ClassVisitor, AttributeVisitor, InstructionVisitor { private static final Logger logger = LogManager.getLogger(ControlFlowObfuscation.class); private static final boolean DEBUG = false; private static final String OPAQUE_FIELD_0 = "OPAQUE_0"; private final Random rand; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); private final ReferenceTracingValueFactory referenceTracingValueFactory = new ReferenceTracingValueFactory(new TypedReferenceValueFactory()); private final PartialEvaluator partialEvaluator = new PartialEvaluator(referenceTracingValueFactory, new ReferenceTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory)), /*evaluateAllCode=*/true, referenceTracingValueFactory); public ControlFlowObfuscation(int seed) { rand = new Random((long)seed); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { if (clazz instanceof ProgramClass) { visitProgramClass((ProgramClass)clazz); } } @Override public void visitProgramClass(ProgramClass programClass) { if ((programClass.getAccessFlags() & AccessConstants.INTERFACE) != 0) { return; } // On a flagged program class, add an new field that is used for an opaque condition prepareClassFields(programClass); programClass.accept(new AllMethodVisitor( new AllAttributeVisitor(this))); } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Start by evaluating the current bytecode partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); codeAttributeEditor.reset(codeAttribute.u4codeLength); if (DEBUG) { logger.info("=== {}.{} ===", clazz.getName(), method.getName(clazz)); } if (DEBUG) { showEvaluatedInst(clazz, method, codeAttribute); } // Process to the modification and commit the modifications (if any) codeAttribute.instructionsAccept(clazz, method, this); codeAttribute.accept(clazz, method, codeAttributeEditor); if (DEBUG) { logger.info("[+] -> New Instructions:"); showInst(clazz, method, codeAttribute); logger.info("[-] <- New Instructions"); // This can be used to early detect inconsistency's partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); } } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { } @Override public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branch) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz); // Currently, this pass only targets GOTO instructions if (branch.opcode != Instruction.OP_GOTO) { return; } FrameFinder finder = new FrameFinder(this.partialEvaluator, offset); codeAttribute.instructionsAccept(clazz, method, finder); if (finder.targets.isEmpty()) { // We can't find a suitable location to // redirect the goto. Hence, we inject a new block CodeAttributeEditor.Label OPAQUE_BLOCK = codeAttributeEditor.label(); float randomFloat = rand.nextFloat(); ____.label(OPAQUE_BLOCK) .ldc(randomFloat) .ldc((float)getInvariantValue(255)) .fmul() .f2i() .getstatic(clazz.getName(), OPAQUE_FIELD_0, "I") .iadd() .putstatic(clazz.getName(), OPAQUE_FIELD_0, "I"); insertOpaquePredicate(clazz, ____) .ifne(branch.branchOffset) .goto_(OPAQUE_BLOCK.offset()); codeAttributeEditor.replaceInstruction(offset, ____.instructions()); } else { int idx = rand.nextInt(finder.targets.size()); int target = finder.targets.get(idx); int reltarget = target - offset; if (DEBUG) logger.info("[{}] -> [{}]", offset, target); // Insert an opaque predicate that is **always** != 0 insertOpaquePredicate(clazz, ____) // **always** != 0 => This branch is **always** taken (original goto's offset) .ifne(branch.branchOffset) // Opaque branch: jump randomly to another suitable instruction .goto_(reltarget); // Replace the original goto instruction with the previous instructions codeAttributeEditor.replaceInstruction(offset, ____.instructions()); } } int getInvariantValue(int range) { int X = rand.nextInt(range); X -= X % 2; return X; } private InstructionSequenceBuilder insertOpaquePredicate(Clazz clazz, InstructionSequenceBuilder ____) { final class OPAQUE_PREDICATES { public static final int OP_0 = 0; public static final int OP_1 = 1; public static final int OP_2 = 2; public static final int LEN = 3; } int id = rand.nextInt(OPAQUE_PREDICATES.LEN); switch (id) { // (X + 1) % 2 != 0 case OPAQUE_PREDICATES.OP_0: { ____.getstatic(clazz.getName(), OPAQUE_FIELD_0, "I") .iconst_1() .iadd() .iconst_2() .irem(); return ____; } // (X ^ 2 + X + 7) mod 81 != 0 case OPAQUE_PREDICATES.OP_1: { ____.bipush(getInvariantValue(128)) .putstatic(clazz.getName(), OPAQUE_FIELD_0, "I") .getstatic(clazz.getName(), OPAQUE_FIELD_0, "I") .getstatic(clazz.getName(), OPAQUE_FIELD_0, "I") .imul() .getstatic(clazz.getName(), OPAQUE_FIELD_0, "I") .iadd() .bipush(7) .iadd() .bipush(81) .irem(); return ____; } // 7y ^ 2 − 1 != x // -> 7 (x + RND) ^ 2 - 1 - x != 0 case OPAQUE_PREDICATES.OP_2: { int var = getInvariantValue(128); ____.bipush(7) .getstatic(clazz.getName(), OPAQUE_FIELD_0, "I") .ldc(var) .iadd() // (X + RND) .getstatic(clazz.getName(), OPAQUE_FIELD_0, "I") .ldc(var) .iadd() // (X + RND) .imul() // ^ 2 .imul() // * 7 .iconst_1() .isub() .getstatic(clazz.getName(), OPAQUE_FIELD_0, "I") .isub(); return ____; } } return ____; } /* * Add and initialize new field(s) so that it can be used to generate opaque predicates * * OPAQUE_FIELD_0 should follow an invariant on which opaque conditions can rely on. * In the current form, the invariant is: "OPAQUE_FIELD_0 must be even" */ private void prepareClassFields(ProgramClass programClass) { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass); ClassEditor classEditor = new ClassEditor(programClass); int nameIndex = constantPoolEditor.addUtf8Constant(OPAQUE_FIELD_0); int descriptorIndex = constantPoolEditor.addUtf8Constant("I"); ProgramField opaqueField = new ProgramField(AccessConstants.PRIVATE | AccessConstants.STATIC, nameIndex, descriptorIndex, null); classEditor.addField(opaqueField); final int value = getInvariantValue(Integer.MAX_VALUE - 1); new InitializerEditor(programClass).addStaticInitializerInstructions(/*mergeIntoExistingInitializer=*/true, /* Initialize OPAQUE_FIELD_0 with the invariant */ ____ -> { ____.ldc(value) .putstatic(programClass, opaqueField); }); } /* * This Class Visitor is used to find instructions * which share the same stack/local frames */ private static class FrameFinder implements InstructionVisitor { public final PartialEvaluator evaluator; public final int offset; public final TracedVariables variables; public final TracedStack stack; public final ArrayList targets = new ArrayList(); public FrameFinder(PartialEvaluator evaluator, int offset) { this.evaluator = evaluator; this.offset = offset; this.variables = evaluator.getVariablesBefore(offset); this.stack = evaluator.getStackBefore(offset); } @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { if (instruction instanceof BranchInstruction) { return; } if (offset == this.offset) { return; } TracedStack stack = evaluator.getStackBefore(offset); if (stack.size() != this.stack.size()) { return; } TracedVariables vars = evaluator.getVariablesBefore(offset); if (vars.size() != this.variables.size()) { return; } for (int index = 0; index < stack.size(); ++index) { Value target = stack.getBottom(index); Value current = this.stack.getBottom(index); if (current == null && target != null) return; if (current == null && target == null) continue; if (!current.equals(target) ) return; } for (int index = 0; index < vars.size(); ++index) { Value target = vars.getValue(index); Value current = this.variables.getValue(index); if (current == null && target != null) return; if (current == null && target == null) continue; if (!current.equals(target) ) return; } targets.add(offset); } } // Helpers for debugging private void showInst(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttribute.instructionsAccept(clazz, method, new InstructionVisitor() { @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { logger.info("{}", instruction.toString(clazz, offset)); } }); } private void showEvaluatedInst(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttribute.instructionsAccept(clazz, method, new InstructionVisitor() { @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { TracedStack stackbefore = partialEvaluator.getStackBefore(offset); TracedStack stackafter = partialEvaluator.getStackAfter(offset); TracedVariables varbefore = partialEvaluator.getVariablesBefore(offset); TracedVariables varfter = partialEvaluator.getVariablesAfter(offset); logger.info("{} ({}/{}) {} | {}", String.format("%-70s", instruction.toString(clazz, offset)), stackbefore.size(), stackafter.size(), varbefore, varfter); logger.info("{} ({}/{}) {} | {}", String.format("%-70s", ""), stackbefore.size(), stackafter.size(), stackbefore, stackafter); } }); } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/controlflow/ControlFlowObfuscationInfo.java ================================================ package dprotect.obfuscation.controlflow; import static dprotect.ObfuscationClassSpecification.Level; public class ControlFlowObfuscationInfo { public Level level = Level.NONE; public ControlFlowObfuscationInfo(Level level) { this.level = level; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/controlflow/ControlFlowObfuscationMarker.java ================================================ package dprotect.obfuscation.controlflow; import dprotect.CFObfuscationClassSpecification; import dprotect.ObfuscationClassSpecification; import dprotect.obfuscation.info.ObfuscationInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.*; public class ControlFlowObfuscationMarker implements ClassVisitor, MemberVisitor { private static final Logger logger = LogManager.getLogger(ControlFlowObfuscationMarker.class); private final CFObfuscationClassSpecification spec; public ControlFlowObfuscationMarker(ObfuscationClassSpecification spec) { this.spec = (CFObfuscationClassSpecification)spec; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Flag the class as it enables to quickly determine whether // it should be considered by the obfuscation pass ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(programClass); if (info.controlflow == null) { info.controlflow = new ControlFlowObfuscationInfo(spec.obfuscationLvl); } } // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) { } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(programMethod); if (info.controlflow == null) { info.controlflow = new ControlFlowObfuscationInfo(spec.obfuscationLvl); } } @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(programField); if (info.controlflow == null) { info.controlflow = new ControlFlowObfuscationInfo(spec.obfuscationLvl); } } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/info/ObfuscationInfo.java ================================================ package dprotect.obfuscation.info; import dprotect.obfuscation.arithmetic.ArithmeticObfuscationInfo; import dprotect.obfuscation.constants.ConstantObfuscationInfo; import dprotect.obfuscation.controlflow.ControlFlowObfuscationInfo; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.Field; import proguard.classfile.constant.Constant; public class ObfuscationInfo { public boolean encodeStrings = false; public ArithmeticObfuscationInfo arithmetic = null; public ConstantObfuscationInfo constants = null; public ControlFlowObfuscationInfo controlflow = null; public static void setClassObfuscationInfo(Clazz clazz) { clazz.setObfuscationInfo(new ObfuscationInfo()); } public static void setMethodObfuscationInfo(Method meth) { meth.setObfuscationInfo(new ObfuscationInfo()); } public static void setFieldObfuscationInfo(Field field) { field.setObfuscationInfo(new ObfuscationInfo()); } public static void setConstantObfuscationInfo(Constant constant) { constant.setObfuscationInfo(new ObfuscationInfo()); } public static ObfuscationInfo getObfuscationInfo(Clazz clazz) { return (ObfuscationInfo)clazz.getObfuscationInfo(); } public static ObfuscationInfo getObfuscationInfo(Method meth) { return (ObfuscationInfo)meth.getObfuscationInfo(); } public static ObfuscationInfo getObfuscationInfo(Field field) { return (ObfuscationInfo)field.getObfuscationInfo(); } public static ObfuscationInfo getObfuscationInfo(Constant constant) { return (ObfuscationInfo)constant.getObfuscationInfo(); } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/info/ObfuscationInfoSetter.java ================================================ package dprotect.obfuscation.info; import proguard.classfile.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.MemberVisitor; import proguard.classfile.constant.Constant; public class ObfuscationInfoSetter implements ClassVisitor, MemberVisitor, ConstantVisitor { private final boolean overwrite; public ObfuscationInfoSetter() { this(false); } public ObfuscationInfoSetter(boolean overwrite) { this.overwrite = overwrite; } // Implementation for ClassVisitor @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { if (programClass.getObfuscationInfo() == null || overwrite) { ObfuscationInfo.setClassObfuscationInfo(programClass); } programClass.constantPoolEntriesAccept(this); } // Implementation for MemberVisitor @Override public void visitAnyMember(Clazz clazz, Member member) { } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (programMethod.getObfuscationInfo() == null || overwrite) { ObfuscationInfo.setMethodObfuscationInfo(programMethod); } } @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { if (programField.getObfuscationInfo() == null || overwrite) { ObfuscationInfo.setFieldObfuscationInfo(programField); } } @Override public void visitAnyConstant(Clazz clazz, Constant constant) { if (constant.getObfuscationInfo() == null || overwrite) { ObfuscationInfo.setConstantObfuscationInfo(constant); } } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/strings/StringFieldMarker.java ================================================ package dprotect.obfuscation.strings; import dprotect.obfuscation.info.ObfuscationInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.visitor.*; import proguard.classfile.instruction.*; import proguard.classfile.attribute.CodeAttribute; // NOTE(romain): I think we could improve this code using the PartialEvaluator public class StringFieldMarker implements InstructionVisitor, ConstantVisitor { private static final Logger logger = LogManager.getLogger(StringFieldMarker.class); private StringConstant stringConstant; // Implementations for MemberVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { } @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_LDC || constantInstruction.opcode == Instruction.OP_LDC_W || constantInstruction.opcode == Instruction.OP_PUTFIELD || constantInstruction.opcode == Instruction.OP_PUTSTATIC) { clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } } // Implementations for ConstantVisitor. @Override public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { Field field = fieldrefConstant.referencedField; if (ObfuscationInfo.getObfuscationInfo(field).encodeStrings && stringConstant != null) { ObfuscationInfo.getObfuscationInfo(stringConstant).encodeStrings = true; } this.stringConstant = null; } @Override public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { this.stringConstant = stringConstant; } @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} } ================================================ FILE: base/src/main/java/dprotect/obfuscation/strings/StringObfuscationMarker.java ================================================ package dprotect.obfuscation.strings; import dprotect.obfuscation.info.ObfuscationInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.*; public class StringObfuscationMarker implements ClassVisitor, MemberVisitor { private static final Logger logger = LogManager.getLogger(StringObfuscationMarker.class); // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Flag the class as it enables to quickly determine whether // it should be considered by the obfuscation pass ObfuscationInfo.getObfuscationInfo(programClass).encodeStrings = true; } // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) { } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { ObfuscationInfo.getObfuscationInfo(programMethod).encodeStrings = true; } @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { ObfuscationInfo.getObfuscationInfo(programField).encodeStrings = true; } } ================================================ FILE: base/src/main/java/dprotect/obfuscation/strings/StringObfuscator.java ================================================ package dprotect.obfuscation.strings; import dprotect.obfuscation.info.ObfuscationInfo; import dprotect.runtime.strings.StringEncoding; import dprotect.runtime.util.Helpers; import dprotect.runtime.util.Loader; import dprotect.util.MethodCopier; import dprotect.obfuscation.controlflow.ControlFlowObfuscationInfo; import dprotect.obfuscation.arithmetic.ArithmeticObfuscationInfo; import static dprotect.ObfuscationClassSpecification.Level; import java.util.List; import java.util.Random; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.Constant; import proguard.classfile.constant.visitor.*; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.*; import proguard.classfile.visitor.*; import proguard.util.*; public class StringObfuscator implements ClassVisitor, AttributeVisitor, InstructionVisitor, ConstantVisitor { private static final Logger logger = LogManager.getLogger(StringObfuscator.class); public static final char KEY_ID = 0xFAFB; private final ClassPool runtime; private ConstantPoolEditor constantPoolEditor; private String replacement = null; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); private final Random rand; private char currentKey = 0; private boolean isMethodEligible = false; private boolean isConstantEligible = false; private final StringMatcher filter; private StringEncoding.EncodingPair selectedEncoding; public StringObfuscator(List strings, int seed) { runtime = Loader.getRuntimeClasses(); rand = new Random((long)seed); filter = strings != null && !strings.isEmpty() ? new ListParser(new NameParser()).parse(strings) : new ConstantMatcher(false); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { int methodId = rand.nextInt(StringEncoding.ENCODING_METHODS.size()); selectedEncoding = StringEncoding.ENCODING_METHODS.get(methodId); try { addDecodeMethod((ProgramClass)clazz, runtime.getClass(Helpers.getNormalizedClassName(StringEncoding.class))); clazz.methodsAccept(new AllAttributeVisitor(this)); } catch (Exception e) { logger.error("Can't add the decoding method: {}", e); } } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { isMethodEligible = ObfuscationInfo.getObfuscationInfo(method).encodeStrings; constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); codeAttributeEditor.reset(codeAttribute.u4codeLength); codeAttribute.instructionsAccept(clazz, method, this); codeAttribute.accept(clazz, method, codeAttributeEditor); } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_LDC || constantInstruction.opcode == Instruction.OP_LDC_W) { replacement = null; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); if (replacement != null) { constantInstruction.constantIndex = constantPoolEditor.addStringConstant(replacement); Method meth = clazz.findMethod(selectedEncoding.decode.getName(), "(Ljava/lang/String;)Ljava/lang/String;"); int index = constantPoolEditor.addMethodrefConstant(clazz, meth); Instruction replacementInstruction = new ConstantInstruction(Instruction.OP_INVOKESTATIC, index); codeAttributeEditor.replaceInstruction(offset, constantInstruction); codeAttributeEditor.insertAfterInstruction(offset, replacementInstruction); } } } // Implementations for ConstantVisitor. @Override public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(stringConstant); isConstantEligible = (info != null && info.encodeStrings); clazz.constantPoolEntryAccept(stringConstant.u2stringIndex, this); } @Override public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { String original = utf8Constant.getString(); boolean shouldProtect = isMethodEligible || (!isMethodEligible && filter.matches(original)) || (!isMethodEligible && isConstantEligible); if (!shouldProtect) { return; } try { replacement = (String)selectedEncoding.encode.invoke(/* static */null, original, currentKey); } catch (Exception e) { logger.error("{}", e); } } @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} void addDecodeMethod(ProgramClass target, Clazz src) { ClassBuilder builder = new ClassBuilder(target); String decodeName = selectedEncoding.decode.getName(); ProgramMethod meth = builder.addAndReturnMethod( AccessConstants.PUBLIC | AccessConstants.STATIC, decodeName, "(Ljava/lang/String;)Ljava/lang/String;"); Method srcDecoding = src.findMethod(decodeName, "(Ljava/lang/String;)Ljava/lang/String;"); if (srcDecoding == null) { logger.fatal("Can't find {}", decodeName); return; } ProgramClass targetProgramClass = builder.getProgramClass(); MethodCopier.copy(targetProgramClass, meth, src, srcDecoding); Method insertedDecode = target.findMethod(decodeName, "(Ljava/lang/String;)Ljava/lang/String;"); markDecodeMethod(insertedDecode); currentKey = (char)rand.nextInt(0xFFFF); insertedDecode.accept(target, new AllAttributeVisitor( new KeyChanger(currentKey))); } void markDecodeMethod(Method method) { ObfuscationInfo.setMethodObfuscationInfo(method); ObfuscationInfo info = ObfuscationInfo.getObfuscationInfo(method); info.controlflow = new ControlFlowObfuscationInfo(Level.LOW); info.arithmetic = new ArithmeticObfuscationInfo(Level.LOW); } static private class KeyChanger implements AttributeVisitor, InstructionVisitor, ConstantVisitor { private ConstantPoolEditor constantPoolEditor; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); private final char newKey; private boolean isKey = false; public KeyChanger(char newKey) { this.newKey = newKey; } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { this.constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); codeAttributeEditor.reset(codeAttribute.u4codeLength); codeAttribute.instructionsAccept(clazz, method, this); codeAttribute.accept(clazz, method, codeAttributeEditor); } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { } @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_LDC || constantInstruction.opcode == Instruction.OP_LDC_W) { isKey = false; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); if (isKey) { int index = constantPoolEditor.addIntegerConstant(newKey); codeAttributeEditor.replaceInstruction( offset, new ConstantInstruction(constantInstruction.opcode, index)); } } } // Implementations for ConstantVisitor. @Override public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant) { isKey = (integerConstant.getValue() == KEY_ID); } } } ================================================ FILE: base/src/main/java/dprotect/runtime/strings/StringEncoding.java ================================================ package dprotect.runtime.strings; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class StringEncoding { public static class EncodingPair { public Method encode; public Method decode; public EncodingPair(Method encode, Method decode) { this.encode = encode; this.decode = decode; } } private static final Logger logger = LogManager.getLogger(StringEncoding.class); public static List ENCODING_METHODS; /* * Register the decode/encode that aim at being used to protect * Strings */ static { try { StringEncoding.ENCODING_METHODS = Arrays.asList( new EncodingPair(StringEncoding.class.getMethod("simpleXorEncode", String.class, char.class), StringEncoding.class.getMethod("simpleXorDecode", String.class)), new EncodingPair(StringEncoding.class.getMethod("simpleIndexedXorEncode", String.class, char.class), StringEncoding.class.getMethod("simpleIndexedXorDecode", String.class)), new EncodingPair(StringEncoding.class.getMethod("fibonnaciLFSREncode", String.class, char.class), StringEncoding.class.getMethod("fibonnaciLFSRDecode", String.class)) ); } catch (NoSuchMethodException e) { logger.fatal("{}", e); } } /////////////////////////////////////////////////////////////////////////// // Encoding/Decoding methods: (0xFAFB is a "magic" number dynamically replaced) /////////////////////////////////////////////////////////////////////////// /* * Simple xor */ public static String simpleXorDecode(String str) { StringBuilder out = new StringBuilder(); for (int i = 0; i < str.length(); ++i) { char x = str.charAt(i); out.append((char) (x ^ (char) 0xFAFB)); } return out.toString(); } public static String simpleXorEncode(String str, char key) { StringBuilder out = new StringBuilder(); for (int i = 0; i < str.length(); ++i) { char x = str.charAt(i); out.append((char) (x ^ (char) key)); } return out.toString(); } /* * Simple xor that also involves the character's position */ public static String simpleIndexedXorDecode(String str) { StringBuilder out = new StringBuilder(); for (int i = 0; i < str.length(); ++i) { char x = str.charAt(i); out.append((char) (x ^ (char) 0xFAFB ^ (i % (char) 0xFFFF))); } return out.toString(); } public static String simpleIndexedXorEncode(String str, char key) { StringBuilder out = new StringBuilder(); for (int i = 0; i < str.length(); ++i) { char x = str.charAt(i); out.append((char) (x ^ (char) key ^ (i % (char) 0xFFFF))); } return out.toString(); } /* * Use a Fibonnaci LFSR stream */ public static String fibonnaciLFSRDecode(String str) { char state = (char)0xFAFB; char lfsr = state; char bit; StringBuilder out = new StringBuilder(); for (int i = 0; i < str.length(); ++i) { bit = (char)(((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1); lfsr = (char)((lfsr >> 1) | (bit << 15)); char x = str.charAt(i); out.append((char)(x ^ lfsr)); } return out.toString(); } public static String fibonnaciLFSREncode(String str, char key) { char state = (char)key; char lfsr = state; char bit; StringBuilder out = new StringBuilder(); for (int i = 0; i < str.length(); ++i) { bit = (char)(((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1); lfsr = (char)((lfsr >> 1) | (bit << 15)); char x = str.charAt(i); out.append((char)(x ^ lfsr)); } return out.toString(); } } ================================================ FILE: base/src/main/java/dprotect/runtime/util/Helpers.java ================================================ package dprotect.runtime.util; public class Helpers { public static String getNormalizedClassName(Class cls) { return cls.getCanonicalName().replace('.', '/'); } } ================================================ FILE: base/src/main/java/dprotect/runtime/util/Loader.java ================================================ package dprotect.runtime.util; import proguard.classfile.*; import proguard.io.*; import proguard.classfile.visitor.*; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; /** * This class is used to load bytecode file (.class) as a Proguard ClassPool */ public class Loader { public static File currentJar() throws URISyntaxException { return new File(Loader.class.getProtectionDomain().getCodeSource().getLocation().toURI()); } public static ClassPool getRuntimeClasses() { ClassPool classPool = new ClassPool(); try { DataEntrySource source = new FileSource( new File(currentJar().getPath())); DataEntryReader classReader = new NameFilteredDataEntryReader("dprotect/runtime/**.class", new ClassReader(false, false, false, false, null, new ClassNameFilter("**", new ClassPoolFiller(classPool)))); classReader = new JarReader(classReader); source.pumpDataEntries(classReader); return classPool; } catch (URISyntaxException | IOException e) { e.printStackTrace(); return classPool; } } } ================================================ FILE: base/src/main/java/proguard/AfterInitConfigurationVerifier.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.ClassVisitor; import proguard.pass.Pass; /** * This pass performs configuration checks for which class pools or resource information * should already have been initialized. */ public class AfterInitConfigurationVerifier implements Pass { private final Configuration configuration; private static final Logger logger = LogManager.getLogger(AfterInitConfigurationVerifier.class); public AfterInitConfigurationVerifier(Configuration configuration) { this.configuration = configuration; } @Override public void execute(AppView appView) { if (configuration.targetClassVersion != 0) { // Fail if -target is set and program class pool contains a class with class version > 11. appView.programClassPool.classesAccept(new BackportMaxVersionVisitor(VersionConstants.CLASS_VERSION_11, configuration.targetClassVersion)); } } private static class BackportMaxVersionVisitor implements ClassVisitor { private final int maxClassFileVersion; private final int target; private BackportMaxVersionVisitor(int maxClassFileVersion, int target) { this.maxClassFileVersion = maxClassFileVersion; this.target = target; } // Implementations of ClassVisitor. @Override public void visitProgramClass(ProgramClass programClass) { if (programClass.u4version > maxClassFileVersion) { if (programClass.u4version != target) { throw new RuntimeException("-target can only be used with class file versions <= " + ClassUtil.internalMajorClassVersion(maxClassFileVersion) + " (Java " + ClassUtil.externalClassVersion(maxClassFileVersion) + ")." + System.lineSeparator() + "The input classes contain version " + ClassUtil.internalMajorClassVersion(programClass.u4version) + " class files which cannot be backported to target version (" + ClassUtil.internalMajorClassVersion(target) + ")."); } logger.warn( "-target is deprecated when using class file above "+ ClassUtil.internalMajorClassVersion(maxClassFileVersion) + " (Java " + ClassUtil.externalClassVersion(maxClassFileVersion) + ")." ); } } @Override public void visitAnyClass(Clazz clazz) { } } } ================================================ FILE: base/src/main/java/proguard/AppView.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard; import proguard.classfile.*; import proguard.configuration.InitialStateInfo; import proguard.io.ExtraDataEntryNameMap; import proguard.resources.file.ResourceFilePool; public class AppView { // App model. public final ClassPool programClassPool; public final ClassPool libraryClassPool; public final ResourceFilePool resourceFilePool; public final ExtraDataEntryNameMap extraDataEntryNameMap; /** * Stores information about the original state of the program class pool used for configuration debugging. */ public InitialStateInfo initialStateInfo; public AppView(ClassPool programClassPool, ClassPool libraryClassPool) { this(programClassPool, libraryClassPool, new ResourceFilePool(), new ExtraDataEntryNameMap()); } public AppView() { this(new ClassPool(), new ClassPool(), new ResourceFilePool(), new ExtraDataEntryNameMap()); } public AppView(ClassPool programClassPool, ClassPool libraryClassPool, ResourceFilePool resourceFilePool, ExtraDataEntryNameMap extraDataEntryNameMap) { this.programClassPool = programClassPool; this.resourceFilePool = resourceFilePool; this.libraryClassPool = libraryClassPool; this.extraDataEntryNameMap = extraDataEntryNameMap; } } ================================================ FILE: base/src/main/java/proguard/ArgumentWordReader.java ================================================ /* * 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; import java.io.*; /** * A WordReader that returns words from an argument list. * Single arguments are split into individual words if necessary. * * @author Eric Lafortune */ public class ArgumentWordReader extends WordReader { private final String[] arguments; private int index = 0; // /** // * Creates a new ArgumentWordReader for the given arguments. // */ // public ArgumentWordReader(String[] arguments) // { // this(arguments, null); // } // // /** * Creates a new ArgumentWordReader for the given arguments, with the * given base directory. */ public ArgumentWordReader(String[] arguments, File baseDir) { super(baseDir); this.arguments = arguments; } // Implementations for WordReader. protected String nextLine() throws IOException { return index < arguments.length ? arguments[index++] : null; } protected String lineLocationDescription() { return "argument number " + index; } /** * Test application that prints out the individual words of * the argument list. */ public static void main(String[] args) { try { WordReader reader = new ArgumentWordReader(args, null); try { while (true) { String word = reader.nextWord(false, false); if (word == null) System.exit(-1); System.err.println("["+word+"]"); } } catch (Exception ex) { ex.printStackTrace(); } finally { reader.close(); } } catch (IOException ex) { ex.printStackTrace(); } } } ================================================ FILE: base/src/main/java/proguard/AssumeNoSideEffectsChecker.java ================================================ /* * 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; import proguard.classfile.util.*; import java.util.List; /** * This class checks if the user is specifying to assume no side effects * for a reasonable number of methods in a class: not none and not all. * * @author Eric Lafortune */ public class AssumeNoSideEffectsChecker { private final WarningPrinter notePrinter; /** * Creates a new KeepClassMemberChecker. */ public AssumeNoSideEffectsChecker(WarningPrinter notePrinter) { this.notePrinter = notePrinter; } /** * Checks if the given class specifications try to assume no side effects * for all methods in a class, printing notes if necessary. */ public void checkClassSpecifications(List classSpecifications) { if (classSpecifications != null) { for (int classSpecificationIndex = 0; classSpecificationIndex < classSpecifications.size(); classSpecificationIndex++) { ClassSpecification classSpecification = (ClassSpecification)classSpecifications.get(classSpecificationIndex); String className = classSpecification.className; if (className == null) { className = classSpecification.extendsClassName; } if (className == null || notePrinter.accepts(className)) { List methodSpecifications = classSpecification.methodSpecifications; if (methodSpecifications != null) { for (int methodSpecificationIndex = 0; methodSpecificationIndex < methodSpecifications.size(); methodSpecificationIndex++) { final MemberSpecification methodSpecification = (MemberSpecification)methodSpecifications.get(methodSpecificationIndex); if (methodSpecification.name == null && methodSpecification.descriptor == null) { notePrinter.print(className == null ? ConfigurationConstants.ANY_CLASS_KEYWORD : className, "Note: the configuration specifies that none of the methods of class '" + (className == null ? ConfigurationConstants.ANY_CLASS_KEYWORD : ClassUtil.externalClassName(className)) + "' have any side effects"); } } } } } } } } ================================================ FILE: base/src/main/java/proguard/ClassMemberChecker.java ================================================ /* * 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.info.ReadWriteFieldMarker; import java.util.List; /** * This class checks if the user has specified non-existent class members. * * @author Eric Lafortune */ public class ClassMemberChecker implements MemberVisitor { private static final Logger logger = LogManager.getLogger(ClassMemberChecker.class); private final ClassPool programClassPool; private final WarningPrinter notePrinter; /** * Creates a new ClassMemberChecker. */ public ClassMemberChecker(ClassPool programClassPool, WarningPrinter notePrinter) { this.programClassPool = programClassPool; this.notePrinter = notePrinter; } /** * Checks the classes mentioned in the given class specifications, printing * notes if necessary. */ public void checkClassSpecifications(List classSpecifications) { if (classSpecifications != null) { for (int index = 0; index < classSpecifications.size(); index++) { ClassSpecification classSpecification = (ClassSpecification)classSpecifications.get(index); String className = classSpecification.className; if (className != null && !containsWildCards(className) && notePrinter.accepts(className)) { Clazz clazz = programClassPool.getClass(className); if (clazz != null) { checkMemberSpecifications(clazz, classSpecification.fieldSpecifications, true); checkMemberSpecifications(clazz, classSpecification.methodSpecifications, false); } } } } } /** * Checks the class members mentioned in the given class member * specifications, printing notes if necessary. */ private void checkMemberSpecifications(Clazz clazz, List memberSpecifications, boolean isField) { if (memberSpecifications != null) { String className = clazz.getName(); for (int index = 0; index < memberSpecifications.size(); index++) { MemberSpecification memberSpecification = (MemberSpecification)memberSpecifications.get(index); String memberName = memberSpecification.name; String descriptor = memberSpecification.descriptor; if (memberName != null && !containsWildCards(memberName) && descriptor != null && !containsWildCards(descriptor)) { if (isField) { if (clazz.findField(memberName, descriptor) == null) { notePrinter.print(className, "Note: the configuration refers to the unknown field '" + ClassUtil.externalFullFieldDescription(0, memberName, descriptor) + "' in class '" + ClassUtil.externalClassName(className) + "'"); } } else { if (clazz.findMethod(memberName, descriptor) == null) { notePrinter.print(className, "Note: the configuration refers to the unknown method '" + ClassUtil.externalFullMethodDescription(className, 0, memberName, descriptor) + "' in class '" + ClassUtil.externalClassName(className) + "'"); } } } } } } private static boolean containsWildCards(String string) { return string != null && (string.indexOf('!') >= 0 || string.indexOf('*') >= 0 || string.indexOf('?') >= 0 || string.indexOf(',') >= 0 || string.indexOf("///") >= 0); } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { logger.info(" Maybe you meant the field '{}'?", ClassUtil.externalFullFieldDescription(0, programField.getName(programClass), programField.getDescriptor(programClass))); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { logger.info(" Maybe you meant the method '{}'?", ClassUtil.externalFullMethodDescription(programClass.getName(), 0, programMethod.getName(programClass), programMethod.getDescriptor(programClass))); } } ================================================ FILE: base/src/main/java/proguard/ClassPath.java ================================================ /* * 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; import java.util.*; /** * This class represents a class path, as a list of ClassPathEntry objects. * * @author Eric Lafortune */ public class ClassPath { private final List classPathEntries = new ArrayList(); /** * Returns whether the class path contains any output entries. */ public boolean hasOutput() { for (int index = 0; index < classPathEntries.size(); index++) { if (((ClassPathEntry)classPathEntries.get(index)).isOutput()) { return true; } } return false; } // Delegates to List. public void clear() { classPathEntries.clear(); } public void add(int index, ClassPathEntry classPathEntry) { classPathEntries.add(index, classPathEntry); } public boolean add(ClassPathEntry classPathEntry) { return classPathEntries.add(classPathEntry); } public boolean addAll(ClassPath classPath) { return classPathEntries.addAll(classPath.classPathEntries); } public ClassPathEntry get(int index) { return (ClassPathEntry)classPathEntries.get(index); } public ClassPathEntry remove(int index) { return (ClassPathEntry)classPathEntries.remove(index); } public boolean isEmpty() { return classPathEntries.isEmpty(); } public int size() { return classPathEntries.size(); } } ================================================ FILE: base/src/main/java/proguard/ClassPathEntry.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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; import proguard.util.ListUtil; import java.io.*; import java.util.List; /** * This class represents an entry from a class path: an apk, a jar, an aar, a * war, a zip, an ear, or a directory. It has a name, and a flag to indicate * whether the entry is an input entry or an output entry. * * It also has an optional feature name that can serve as a tag to indicate * the group to which the entry belongs, e.g. a given Android dynamic feature. * * It also has optional filters for the names of the contained * resource/classes, apks, jars, aars, wars, ears, and zips. * * @author Eric Lafortune */ public class ClassPathEntry { private File file; private boolean output; private String featureName; private List filter; private List apkFilter; private List aabFilter; private List jarFilter; private List aarFilter; private List warFilter; private List earFilter; private List jmodFilter; private List zipFilter; private String cachedName; /** * Creates a new ClassPathEntry with the given file and output flag. */ public ClassPathEntry(File file, boolean isOutput) { this.file = file; this.output = isOutput; } /** * Creates a new ClassPathEntry with the given file, output flag, * and optional feature name. */ public ClassPathEntry(File file, boolean isOutput, String featureName) { this.file = file; this.output = isOutput; this.featureName = featureName; } /** * Returns the path name of the entry. */ public String getName() { if (cachedName == null) { cachedName = getUncachedName(); } return cachedName; } /** * Returns the uncached path name of the entry. */ private String getUncachedName() { try { return file.getCanonicalPath(); } catch (IOException ex) { return file.getPath(); } } /** * Returns the file. */ public File getFile() { return file; } /** * Sets the file. */ public void setFile(File file) { this.file = file; this.cachedName = null; } /** * Returns whether this data entry is an output entry. */ public boolean isOutput() { return output; } /** * Specifies whether this data entry is an output entry. */ public void setOutput(boolean output) { this.output = output; } /** * Returns the feature name. */ public String getFeatureName() { return featureName; } /** * Sets the feature name. */ public void setFeatureName(String featureName) { this.featureName = featureName; } /** * Returns whether this data entry is a dex file. */ public boolean isDex() { return hasExtension(".dex"); } /** * Returns whether this data entry is an apk file. */ public boolean isApk() { return hasExtension(".apk") || hasExtension(".ap_"); } /** * Returns whether this data entry is an aab file. */ public boolean isAab() { return hasExtension(".aab"); } /** * Returns whether this data entry is a jar file. */ public boolean isJar() { return hasExtension(".jar"); } /** * Returns whether this data entry is an aar file. */ public boolean isAar() { return hasExtension(".aar"); } /** * Returns whether this data entry is a war file. */ public boolean isWar() { return hasExtension(".war"); } /** * Returns whether this data entry is a ear file. */ public boolean isEar() { return hasExtension(".ear"); } /** * Returns whether this data entry is a jmod file. */ public boolean isJmod() { return hasExtension(".jmod"); } /** * Returns whether this data entry is a zip file. */ public boolean isZip() { return hasExtension(".zip"); } /** * Returns whether this data entry has the given extension. */ private boolean hasExtension(String extension) { return endsWithIgnoreCase(file.getPath(), extension); } /** * Returns whether the given string ends with the given suffix, ignoring * its case. */ private static boolean endsWithIgnoreCase(String string, String suffix) { int stringLength = string.length(); int suffixLength = suffix.length(); return string.regionMatches(true, stringLength - suffixLength, suffix, 0, suffixLength); } /** * Returns whether this data entry has any kind of filter. */ public boolean isFiltered() { return filter != null || apkFilter != null || aabFilter != null || jarFilter != null || aarFilter != null || warFilter != null || earFilter != null || jmodFilter != null || zipFilter != null; } /** * Returns the name filter that is applied to bottom-level files in this entry. */ public List getFilter() { return filter; } /** * Sets the name filter that is applied to bottom-level files in this entry. */ public void setFilter(List filter) { this.filter = filter == null || filter.size() == 0 ? null : filter; } /** * Returns the name filter that is applied to apk files in this entry, if any. */ public List getApkFilter() { return apkFilter; } /** * Sets the name filter that is applied to apk files in this entry, if any. */ public void setApkFilter(List filter) { this.apkFilter = filter == null || filter.size() == 0 ? null : filter; } /** * Returns the name filter that is applied to aab files in this entry, if any. */ public List getAabFilter() { return aabFilter; } /** * Sets the name filter that is applied to aab files in this entry, if any. */ public void setAabFilter(List filter) { this.aabFilter = filter == null || filter.size() == 0 ? null : filter; } /** * Returns the name filter that is applied to jar files in this entry, if any. */ public List getJarFilter() { return jarFilter; } /** * Sets the name filter that is applied to jar files in this entry, if any. */ public void setJarFilter(List filter) { this.jarFilter = filter == null || filter.size() == 0 ? null : filter; } /** * Returns the name filter that is applied to aar files in this entry, if any. */ public List getAarFilter() { return aarFilter; } /** * Sets the name filter that is applied to aar files in this entry, if any. */ public void setAarFilter(List filter) { this.aarFilter = filter == null || filter.size() == 0 ? null : filter; } /** * Returns the name filter that is applied to war files in this entry, if any. */ public List getWarFilter() { return warFilter; } /** * Sets the name filter that is applied to war files in this entry, if any. */ public void setWarFilter(List filter) { this.warFilter = filter == null || filter.size() == 0 ? null : filter; } /** * Returns the name filter that is applied to ear files in this entry, if any. */ public List getEarFilter() { return earFilter; } /** * Sets the name filter that is applied to ear files in this entry, if any. */ public void setEarFilter(List filter) { this.earFilter = filter == null || filter.size() == 0 ? null : filter; } /** * Returns the name filter that is applied to jmod files in this entry, if any. */ public List getJmodFilter() { return jmodFilter; } /** * Sets the name filter that is applied to jmod files in this entry, if any. */ public void setJmodFilter(List filter) { this.jmodFilter = filter == null || filter.size() == 0 ? null : filter; } /** * Returns the name filter that is applied to zip files in this entry, if any. */ public List getZipFilter() { return zipFilter; } /** * Sets the name filter that is applied to zip files in this entry, if any. */ public void setZipFilter(List filter) { this.zipFilter = filter == null || filter.size() == 0 ? null : filter; } // Implementations for Object. public String toString() { String string = getName(); if (filter != null || apkFilter != null || aabFilter != null || jarFilter != null || aarFilter != null || warFilter != null || earFilter != null || jmodFilter != null || zipFilter != null) { string += ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + (aarFilter != null ? ListUtil.commaSeparatedString(aarFilter, true) : "") + ConfigurationConstants.SEPARATOR_KEYWORD + (aabFilter != null ? ListUtil.commaSeparatedString(aabFilter, true) : "") + ConfigurationConstants.SEPARATOR_KEYWORD + (apkFilter != null ? ListUtil.commaSeparatedString(apkFilter, true) : "") + ConfigurationConstants.SEPARATOR_KEYWORD + (zipFilter != null ? ListUtil.commaSeparatedString(zipFilter, true) : "") + ConfigurationConstants.SEPARATOR_KEYWORD + (jmodFilter != null ? ListUtil.commaSeparatedString(jmodFilter, true) : "") + ConfigurationConstants.SEPARATOR_KEYWORD + (earFilter != null ? ListUtil.commaSeparatedString(earFilter, true) : "") + ConfigurationConstants.SEPARATOR_KEYWORD + (warFilter != null ? ListUtil.commaSeparatedString(warFilter, true) : "") + ConfigurationConstants.SEPARATOR_KEYWORD + (jarFilter != null ? ListUtil.commaSeparatedString(jarFilter, true) : "") + ConfigurationConstants.SEPARATOR_KEYWORD + (filter != null ? ListUtil.commaSeparatedString(filter, true) : "") + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD; } return string; } } ================================================ FILE: base/src/main/java/proguard/ClassSpecification.java ================================================ /* * 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; import java.util.*; /** * This class stores a specification of classes and possibly class members. * The specification is template-based: the class names and class member names * and descriptors can contain wildcards. Classes can be specified explicitly, * or as extensions or implementations in the class hierarchy. * * @author Eric Lafortune */ public class ClassSpecification implements Cloneable { public final String comments; public String memberComments; public int requiredSetAccessFlags; public int requiredUnsetAccessFlags; public final String annotationType; public String className; public final String extendsAnnotationType; public final String extendsClassName; public final List attributeNames = null; public List fieldSpecifications; public List methodSpecifications; /** * Creates a new ClassSpecification for all possible classes, without * comments or class members. */ public ClassSpecification() { this(null, 0, 0, null, null, null, null); } /** * Creates a new ClassSpecification that is a copy of the given specification. */ public ClassSpecification(ClassSpecification classSpecification) { this(classSpecification.comments, classSpecification.requiredSetAccessFlags, classSpecification.requiredUnsetAccessFlags, classSpecification.annotationType, classSpecification.className, classSpecification.extendsAnnotationType, classSpecification.extendsClassName, classSpecification.fieldSpecifications, classSpecification.methodSpecifications); } /** * Creates a new ClassSpecification for the specified class(es), without * class members. * * @param comments provides optional comments on this * specification. * @param requiredSetAccessFlags the class access flags that must be set * in order for the class to apply. * @param requiredUnsetAccessFlags the class access flags that must be * unset in order for the class to apply. * @param annotationType the name of the class that must be an * annotation of the class in order for it * to apply. The name may be null to * specify that no annotation is required. * @param className the class name. The name may be null to * specify any class, or it may contain * "**", "*", or "?" wildcards. * @param extendsAnnotationType the name of the class of that must be * an annotation of the class that the * class must extend or implement in order * to apply. The name may be null to * specify that no annotation is required. * @param extendsClassName the name of the class that the class * must extend or implement in order to * apply. The name may be null to specify * any class. */ public ClassSpecification(String comments, int requiredSetAccessFlags, int requiredUnsetAccessFlags, String annotationType, String className, String extendsAnnotationType, String extendsClassName) { this(comments, requiredSetAccessFlags, requiredUnsetAccessFlags, annotationType, className, extendsAnnotationType, extendsClassName, null, null); } /** * Creates a new ClassSpecification for the specified classes and class * members. * * @param comments provides optional comments on this * specification. * @param requiredSetAccessFlags the class access flags that must be set * in order for the class to apply. * @param requiredUnsetAccessFlags the class access flags that must be * unset in order for the class to apply. * @param annotationType the name of the class that must be an * annotation of the class in order for it * to apply. The name may be null to * specify that no annotation is required. * @param className the class name. The name may be null to * specify any class, or it may contain * "**", "*", or "?" wildcards. * @param extendsAnnotationType the name of the class of that must be * an annotation of the class that the * class must extend or implement in order * to apply. The name may be null to * specify that no annotation is required. * @param extendsClassName the name of the class that the class * must extend or implement in order to * apply. The name may be null to specify * any class. * @param fieldSpecifications the field specifications. * @param methodSpecifications the method specifications. */ public ClassSpecification(String comments, int requiredSetAccessFlags, int requiredUnsetAccessFlags, String annotationType, String className, String extendsAnnotationType, String extendsClassName, List fieldSpecifications, List methodSpecifications) { this.comments = comments; this.requiredSetAccessFlags = requiredSetAccessFlags; this.requiredUnsetAccessFlags = requiredUnsetAccessFlags; this.annotationType = annotationType; this.className = className; this.extendsAnnotationType = extendsAnnotationType; this.extendsClassName = extendsClassName; this.fieldSpecifications = fieldSpecifications; this.methodSpecifications = methodSpecifications; } /** * Specifies to keep the specified field(s) of this option's class(es). * * @param fieldSpecification the field specification. */ public void addField(MemberSpecification fieldSpecification) { if (fieldSpecifications == null) { fieldSpecifications = new ArrayList(); } fieldSpecifications.add(fieldSpecification); } /** * Specifies to keep the specified method(s) of this option's class(es). * * @param methodSpecification the method specification. */ public void addMethod(MemberSpecification methodSpecification) { if (methodSpecifications == null) { methodSpecifications = new ArrayList(); } methodSpecifications.add(methodSpecification); } // Implementations for Object. public boolean equals(Object object) { if (object == null || this.getClass() != object.getClass()) { return false; } ClassSpecification other = (ClassSpecification)object; return // (this.comments == null ? other.comments == null : this.comments.equals(other.comments) ) && (this.requiredSetAccessFlags == other.requiredSetAccessFlags ) && (this.requiredUnsetAccessFlags == other.requiredUnsetAccessFlags ) && (this.annotationType == null ? other.annotationType == null : this.annotationType.equals(other.annotationType) ) && (this.className == null ? other.className == null : this.className.equals(other.className) ) && (this.extendsAnnotationType == null ? other.extendsAnnotationType == null : this.extendsAnnotationType.equals(other.extendsAnnotationType)) && (this.extendsClassName == null ? other.extendsClassName == null : this.extendsClassName.equals(other.extendsClassName) ) && (this.fieldSpecifications == null ? other.fieldSpecifications == null : this.fieldSpecifications.equals(other.fieldSpecifications) ) && (this.methodSpecifications == null ? other.methodSpecifications == null : this.methodSpecifications.equals(other.methodSpecifications) ); } public int hashCode() { return // (comments == null ? 0 : comments.hashCode() ) ^ (requiredSetAccessFlags ) ^ (requiredUnsetAccessFlags ) ^ (annotationType == null ? 0 : annotationType.hashCode() ) ^ (className == null ? 0 : className.hashCode() ) ^ (extendsAnnotationType == null ? 0 : extendsAnnotationType.hashCode()) ^ (extendsClassName == null ? 0 : extendsClassName.hashCode() ) ^ (fieldSpecifications == null ? 0 : fieldSpecifications.hashCode() ) ^ (methodSpecifications == null ? 0 : methodSpecifications.hashCode() ); } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } } } ================================================ FILE: base/src/main/java/proguard/ClassSpecificationVisitorFactory.java ================================================ /* * 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; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.visitor.*; import proguard.util.*; import java.util.List; /** * This factory creates visitors to efficiently travel to specified classes and * class members. * * @author Eric Lafortune */ public class ClassSpecificationVisitorFactory { /** * Constructs a ClassPoolVisitor to efficiently travel to the specified * classes, class members, and attributes. * * @param classSpecifications the list of ClassSpecification instances * that specify the classes and class members * to visit. * @param classVisitor an optional ClassVisitor to be applied to * all classes. * @param memberVisitor an optional MemberVisitor to be applied to * matching fields and methods. */ public ClassPoolVisitor createClassPoolVisitor(List classSpecifications, ClassVisitor classVisitor, MemberVisitor memberVisitor) { return createClassPoolVisitor(classSpecifications, classVisitor, memberVisitor, memberVisitor, null); } /** * Constructs a ClassPoolVisitor to efficiently travel to the specified * classes and class members. * * @param classSpecifications the list of ClassSpecification instances * that specify the classes and class members * to visit. * @param classVisitor an optional ClassVisitor to be applied to * all classes. * @param fieldVisitor an optional MemberVisitor to be applied to * matching fields. * @param methodVisitor an optional MemberVisitor to be applied to * matching methods. * @param attributeVisitor an optional AttributeVisitor to be applied * to matching attributes. */ public ClassPoolVisitor createClassPoolVisitor(List classSpecifications, ClassVisitor classVisitor, MemberVisitor fieldVisitor, MemberVisitor methodVisitor, AttributeVisitor attributeVisitor) { MultiClassPoolVisitor multiClassPoolVisitor = new MultiClassPoolVisitor(); if (classSpecifications != null) { for (int index = 0; index < classSpecifications.size(); index++) { ClassSpecification classSpecification = (ClassSpecification)classSpecifications.get(index); multiClassPoolVisitor.addClassPoolVisitor( createClassPoolVisitor(classSpecification, classVisitor, fieldVisitor, methodVisitor, attributeVisitor, null)); } } return multiClassPoolVisitor; } /** * Constructs a ClassPoolVisitor to efficiently travel to the specified * classes, class members, and attributes. * * @param classSpecification the specifications of the class(es) and class * members to visit. * @param classVisitor an optional ClassVisitor to be applied to * matching classes. * @param fieldVisitor an optional MemberVisitor to be applied to * matching fields. * @param methodVisitor an optional MemberVisitor to be applied to * matching methods. * @param attributeVisitor an optional AttributeVisitor to be applied * to matching attributes. * @param wildcardManager an optional scope for StringMatcher * instances that match wildcards. */ protected ClassPoolVisitor createClassPoolVisitor(ClassSpecification classSpecification, ClassVisitor classVisitor, MemberVisitor fieldVisitor, MemberVisitor methodVisitor, AttributeVisitor attributeVisitor, WildcardManager wildcardManager) { String className = classSpecification.className; String annotationType = classSpecification.annotationType; String extendsAnnotationType = classSpecification.extendsAnnotationType; String extendsClassName = classSpecification.extendsClassName; // We explicitly need to match a wildcard class name, so it can be // referenced through its variable string matcher. if (className == null) { className = "**"; } // We need to parse the class names before any class member names, to // make sure the list of variable string matchers is filled out in the // right order. StringMatcher annotationTypeMatcher = annotationType == null ? null : new ListParser(new ClassNameParser(wildcardManager)).parse(annotationType); StringMatcher classNameMatcher = new ListParser(new ClassNameParser(wildcardManager)).parse(className); StringMatcher extendsAnnotationTypeMatcher = extendsAnnotationType == null ? null : new ListParser(new ClassNameParser(wildcardManager)).parse(extendsAnnotationType); StringMatcher extendsClassNameMatcher = extendsClassName == null ? null : new ListParser(new ClassNameParser(wildcardManager)).parse(extendsClassName); // Combine both visitors. ClassVisitor combinedClassVisitor = createCombinedClassVisitor(classSpecification.attributeNames, classSpecification.fieldSpecifications, classSpecification.methodSpecifications, classVisitor, fieldVisitor, methodVisitor, attributeVisitor, wildcardManager); // If the class name has wildcards, only visit classes with matching names. if (extendsAnnotationType != null || extendsClassName != null || containsWildCards(className)) { combinedClassVisitor = new ClassNameFilter(classNameMatcher, combinedClassVisitor); // We'll have to visit all classes now. className = null; } // If specified, only visit classes with the right annotation. if (annotationType != null) { combinedClassVisitor = new AllAttributeVisitor( new AllAnnotationVisitor( new AnnotationTypeFilter(annotationTypeMatcher, new AnnotationToAnnotatedClassVisitor(combinedClassVisitor)))); } // If specified, only visit classes with the right access flags. if (classSpecification.requiredSetAccessFlags != 0 || classSpecification.requiredUnsetAccessFlags != 0) { combinedClassVisitor = new ClassAccessFilter(classSpecification.requiredSetAccessFlags, classSpecification.requiredUnsetAccessFlags, combinedClassVisitor); } // If it's specified, start visiting from the extended class. if (extendsAnnotationType != null || extendsClassName != null) { // Start visiting from the extended class. combinedClassVisitor = new ClassHierarchyTraveler(false, false, false, true, combinedClassVisitor); // If specified, only visit extended classes with the right annotation. if (extendsAnnotationType != null) { combinedClassVisitor = new AllAttributeVisitor( new AllAnnotationVisitor( new AnnotationTypeFilter(extendsAnnotationTypeMatcher, new AnnotationToAnnotatedClassVisitor(combinedClassVisitor)))); } // If specified, only visit extended classes with matching names. if (extendsClassName != null) { // If wildcarded, only visit extended classes with matching names. if (containsWildCards(extendsClassName)) { combinedClassVisitor = new ClassNameFilter(extendsClassNameMatcher, combinedClassVisitor); } else { // Start visiting from the named extended class. className = extendsClassName; } } } return // If specified, visit a single named class. className != null ? new NamedClassVisitor(combinedClassVisitor, className) : // If an extendsClassName is specified, start visiting from matching extendsClassName classes. extendsClassName != null ? new FilteredClassVisitor(extendsClassNameMatcher, combinedClassVisitor) : // If there is a className filter, start visiting from matching className classes. classSpecification.className != null ? new FilteredClassVisitor(classNameMatcher, combinedClassVisitor) : // Otherwise, visit all classes. new AllClassVisitor(combinedClassVisitor); } /** * Constructs a ClassVisitor to efficiently delegate to the given ClassVisitor * and travel to the specified class members and attributes. * @param attributeNames optional names (with wildcards) of class * attributes to visit. * @param fieldSpecifications optional specifications of the fields to * visit. * @param methodSpecifications optional specifications of the methods to * visit. * @param classVisitor an optional ClassVisitor to be applied to * all classes. * @param fieldVisitor an optional MemberVisitor to be applied to * matching fields. * @param methodVisitor an optional MemberVisitor to be applied to * matching methods. * @param attributeVisitor an optional AttributeVisitor to be applied * to matching attributes. * @param wildcardManager an optional scope for StringMatcher * instances that match wildcards. */ protected ClassVisitor createCombinedClassVisitor(List attributeNames, List fieldSpecifications, List methodSpecifications, ClassVisitor classVisitor, MemberVisitor fieldVisitor, MemberVisitor methodVisitor, AttributeVisitor attributeVisitor, WildcardManager wildcardManager) { // Don't visit any members if there aren't any member specifications. if (fieldSpecifications == null) { fieldVisitor = null; } if (methodSpecifications == null) { methodVisitor = null; } // The class visitor for classes and their members. MultiClassVisitor multiClassVisitor = new MultiClassVisitor(); // If specified, let the class visitor visit the class itself. if (classVisitor != null) { // This class visitor may be the only one. if (fieldVisitor == null && methodVisitor == null && attributeVisitor == null) { return classVisitor; } multiClassVisitor.addClassVisitor(classVisitor); } // If specified, let the attribute visitor visit the class attributes. if (attributeVisitor != null) { // If specified, only visit attributes with the right names. if (attributeNames != null) { attributeVisitor = new AttributeNameFilter(attributeNames, attributeVisitor); } multiClassVisitor.addClassVisitor(new AllAttributeVisitor(attributeVisitor)); } // If specified, let the member info visitor visit the class members. if (fieldVisitor != null || methodVisitor != null || attributeVisitor != null) { ClassVisitor memberClassVisitor = createClassVisitor(fieldSpecifications, methodSpecifications, fieldVisitor, methodVisitor, attributeVisitor, wildcardManager); // This class visitor may be the only one. if (classVisitor == null) { return memberClassVisitor; } multiClassVisitor.addClassVisitor(memberClassVisitor); } return multiClassVisitor; } /** * Constructs a ClassVisitor to efficiently travel to the specified class * members. * * @param fieldSpecifications the specifications of the fields to visit. * @param methodSpecifications the specifications of the methods to visit. * @param fieldVisitor an optional MemberVisitor to be applied to * matching fields. * @param methodVisitor an optional MemberVisitor to be applied to * matching methods. * @param attributeVisitor an optional AttributeVisitor to be applied * to matching attributes. * @param wildcardManager an optional scope for StringMatcher * instances that match wildcards. */ private ClassVisitor createClassVisitor(List fieldSpecifications, List methodSpecifications, MemberVisitor fieldVisitor, MemberVisitor methodVisitor, AttributeVisitor attributeVisitor, WildcardManager wildcardManager) { MultiClassVisitor multiClassVisitor = new MultiClassVisitor(); addMemberVisitors(fieldSpecifications, true, multiClassVisitor, fieldVisitor, attributeVisitor, wildcardManager); addMemberVisitors(methodSpecifications, false, multiClassVisitor, methodVisitor, attributeVisitor, wildcardManager); // Mark the class member in this class and in super classes. return new ClassHierarchyTraveler(true, true, false, false, multiClassVisitor); } /** * Adds elements to the given MultiClassVisitor, to apply the given * MemberVisitor to all class members that match the given List * of options (of the given type). */ private void addMemberVisitors(List memberSpecifications, boolean isField, MultiClassVisitor multiClassVisitor, MemberVisitor memberVisitor, AttributeVisitor attributeVisitor, WildcardManager wildcardManager) { if (memberSpecifications != null && memberVisitor != null) { for (int index = 0; index < memberSpecifications.size(); index++) { MemberSpecification memberSpecification = (MemberSpecification)memberSpecifications.get(index); multiClassVisitor.addClassVisitor( createNonTestingClassVisitor(memberSpecification, isField, memberVisitor, attributeVisitor, wildcardManager)); } } } /** * Creates a new ClassVisitor to efficiently travel to the specified class * members and attributes. * * @param memberSpecification the specification of the class member(s) to * visit. * @param memberVisitor the MemberVisitor to be applied to matching * class member(s). * @param wildcardManager an optional scope for StringMatcher * instances that match wildcards. */ protected ClassVisitor createNonTestingClassVisitor(MemberSpecification memberSpecification, boolean isField, MemberVisitor memberVisitor, AttributeVisitor attributeVisitor, WildcardManager wildcardManager) { return createClassVisitor(memberSpecification, isField, memberVisitor, attributeVisitor, wildcardManager); } /** * Constructs a ClassPoolVisitor that conditionally applies the given * ClassPoolVisitor for all classes that match the given class * specification. */ protected ClassPoolVisitor createClassTester(ClassSpecification classSpecification, ClassPoolVisitor classPoolVisitor, WildcardManager wildcardManager) { ClassPoolClassVisitor classPoolClassVisitor = new ClassPoolClassVisitor(classPoolVisitor); // Parse the class condition. ClassPoolVisitor conditionalClassTester = createClassTester(classSpecification, (ClassVisitor)classPoolClassVisitor, wildcardManager); // The ClassPoolClassVisitor first needs to visit the class pool // and then its classes. return new MultiClassPoolVisitor( classPoolClassVisitor, conditionalClassTester ); } /** * Constructs a ClassPoolVisitor that conditionally applies the given * ClassVisitor to all classes that match the given class specification. */ protected ClassPoolVisitor createClassTester(ClassSpecification classSpecification, ClassVisitor classVisitor, WildcardManager wildcardManager) { // Create a placeholder for the class visitor that tests class // members. MultiClassVisitor conditionalMemberTester = new MultiClassVisitor(); // Parse the class condition. ClassPoolVisitor conditionalClassTester = createClassPoolVisitor(classSpecification, conditionalMemberTester, null, null, null, wildcardManager); // Parse the member conditions and add the result to the placeholder. conditionalMemberTester.addClassVisitor( createClassMemberTester(classSpecification.fieldSpecifications, classSpecification.methodSpecifications, classVisitor, wildcardManager)); return conditionalClassTester; } /** * Constructs a ClassVisitor that conditionally applies the given * ClassVisitor to all classes that contain the given class members. */ private ClassVisitor createClassMemberTester(List fieldSpecifications, List methodSpecifications, ClassVisitor classVisitor, WildcardManager wildcardManager) { // Create a linked list of conditional visitors, for fields and for // methods. return createClassMemberTester(fieldSpecifications, true, createClassMemberTester(methodSpecifications, false, classVisitor, wildcardManager), wildcardManager); } /** * Constructs a ClassVisitor that conditionally applies the given * ClassVisitor to all classes that contain the given List of class * members (of the given type). */ private ClassVisitor createClassMemberTester(List memberSpecifications, boolean isField, ClassVisitor classVisitor, WildcardManager wildcardManager) { // Create a linked list of conditional visitors. if (memberSpecifications != null) { for (int index = 0; index < memberSpecifications.size(); index++) { MemberSpecification memberSpecification = (MemberSpecification)memberSpecifications.get(index); classVisitor = createClassVisitor(memberSpecification, isField, new MemberToClassVisitor(classVisitor), null, wildcardManager); } } return classVisitor; } /** * Creates a new ClassVisitor to efficiently travel to the specified class * members and attributes. * * @param memberSpecification the specification of the class member(s) to * visit. * @param memberVisitor the MemberVisitor to be applied to matching * class member(s). * @param wildcardManager a mutable list of VariableStringMatcher * instances that match the wildcards. */ private ClassVisitor createClassVisitor(MemberSpecification memberSpecification, boolean isField, MemberVisitor memberVisitor, AttributeVisitor attributeVisitor, WildcardManager wildcardManager) { String annotationType = memberSpecification.annotationType; String name = memberSpecification.name; String descriptor = memberSpecification.descriptor; List attributeNames = memberSpecification.attributeNames; // We need to parse the names before the descriptors, to make sure the // list of variable string matchers is filled out in the right order. StringMatcher annotationTypeMatcher = annotationType == null ? null : new ListParser(new ClassNameParser(wildcardManager)).parse(annotationType); StringMatcher nameMatcher = name == null ? null : new ListParser(new NameParser(wildcardManager)).parse(name); StringMatcher descriptorMatcher = descriptor == null ? null : new ListParser(new ClassNameParser(wildcardManager)).parse(descriptor); StringMatcher attributesMatcher = attributeNames == null ? null : new ListParser(new NameParser(wildcardManager)).parse(attributeNames); // If specified, let the attribute visitor visit the class member // attributes. if (attributeVisitor != null) { // If specified, only visit attributes with the right names. if (attributesMatcher != null) { attributeVisitor = new AttributeNameFilter(attributesMatcher, attributeVisitor); } memberVisitor = memberVisitor != null ? new MultiMemberVisitor( memberVisitor, new AllAttributeVisitor(attributeVisitor)) : new AllAttributeVisitor(attributeVisitor); } // If specified, only visit class members with the right annotation. if (memberSpecification.annotationType != null) { memberVisitor = new AllAttributeVisitor( new AllAnnotationVisitor( new AnnotationTypeFilter(annotationTypeMatcher, new AnnotationToAnnotatedMemberVisitor(memberVisitor)))); } // If any access flags are specified, only visit matching class members. if (memberSpecification.requiredSetAccessFlags != 0 || memberSpecification.requiredUnsetAccessFlags != 0) { memberVisitor = new MemberAccessFilter(memberSpecification.requiredSetAccessFlags, memberSpecification.requiredUnsetAccessFlags, memberVisitor); } // Are the name and descriptor fully specified? if (name != null && descriptor != null && !containsWildCards(name) && !containsWildCards(descriptor)) { // Somewhat more efficiently, visit a single named class member. return isField ? new NamedFieldVisitor(name, descriptor, memberVisitor) : new NamedMethodVisitor(name, descriptor, memberVisitor); } // If specified, only visit class members with the right descriptors. if (descriptorMatcher != null) { memberVisitor = new MemberDescriptorFilter(descriptorMatcher, memberVisitor); } // If specified, only visit class members with the right names. if (name != null) { memberVisitor = new MemberNameFilter(nameMatcher, memberVisitor); } // Visit all class members, filtering the matching ones. return isField ? new AllFieldVisitor(memberVisitor) : new AllMethodVisitor(memberVisitor); } // Small utility methods. /** * Returns whether the given string contains a wild card. */ private boolean containsWildCards(String string) { return string != null && (string.indexOf('!') >= 0 || string.indexOf('*') >= 0 || string.indexOf('?') >= 0 || string.indexOf('%') >= 0 || string.indexOf(',') >= 0 || string.indexOf("///") >= 0 || containsWildCardReferences(string)); } /** * Returns whether the given string contains a numeric reference to a * wild card (""). */ private boolean containsWildCardReferences(String string) { int openIndex = string.indexOf('<'); if (openIndex < 0) { return false; } int closeIndex = string.indexOf('>', openIndex + 1); if (closeIndex < 0) { return false; } try { Integer.parseInt(string.substring(openIndex + 1, closeIndex)); } catch (NumberFormatException e) { return false; } return true; } } ================================================ FILE: base/src/main/java/proguard/Configuration.java ================================================ /* * 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; import java.io.File; import java.net.URL; import java.util.*; /** * The ProGuard configuration. * * @see ProGuard * * @author Eric Lafortune */ public class Configuration { public static final File STD_OUT = new File(""); /////////////////////////////////////////////////////////////////////////// // Input and output options. /////////////////////////////////////////////////////////////////////////// /** * A list of input and output entries (jars, wars, ears, jmods, zips, and directories). */ public ClassPath programJars; /** * A list of library entries (jars, wars, ears, jmods, zips, and directories). */ public ClassPath libraryJars; /** * Specifies whether to skip non-public library classes while reading * library jars. */ public boolean skipNonPublicLibraryClasses = false; /** * Specifies whether to skip non-public library class members while reading * library classes. */ public boolean skipNonPublicLibraryClassMembers = true; /** * A list of String instances specifying directories to be kept in * the output directories or the output jars. A null list * means no directories. An empty list means all directories. The directory * names may contain "**", "*", or "?" wildcards, and they may be preceded * by the "!" negator. */ public List keepDirectories; /** * A list of String instances specifying a filter for files that should * not be compressed in output jars. */ public List dontCompress; /** * Specifies the desired alignment of uncompressed data in output jars. * Uncompressed data will start at a multiple of the given number of bytes, * relative to the start of the jar file. */ public int zipAlign = 1; /** * Specifies the version number of the output classes, or 0 if the version * number can be left unchanged. */ public int targetClassVersion; /** * Specifies the last modification time of this configuration. This time * is necessary to check whether the input has to be processed. Setting it * to Long.MAX_VALUE forces processing, even if the modification times * of the output appear more recent than the modification times of the * input. */ public long lastModified = 0L; /////////////////////////////////////////////////////////////////////////// // Keep options for code. /////////////////////////////////////////////////////////////////////////// /** * A list of {@link KeepClassSpecification} instances, whose class names and * class member names are to be kept from shrinking, optimization, and/or * obfuscation. */ public List keep; /** * An optional output file for listing the kept seeds. * An empty file name means the standard output. */ public File printSeeds; /////////////////////////////////////////////////////////////////////////// // Shrinking options. /////////////////////////////////////////////////////////////////////////// /** * Specifies whether the code should be shrunk. */ public boolean shrink = true; /** * An optional output file for listing the unused classes and class * members. An empty file name means the standard output. */ public File printUsage; /** * A list of {@link ClassSpecification} instances, for which an explanation * is to be printed, why they are kept in the shrinking step. */ public List whyAreYouKeeping; /////////////////////////////////////////////////////////////////////////// // Optimization options. /////////////////////////////////////////////////////////////////////////// /** * Specifies whether the code should be optimized. */ public boolean optimize = true; /** * A list of String instances specifying the optimizations to be * performed. A null list means all optimizations. The * optimization names may contain "*" or "?" wildcards, and they may * be preceded by the "!" negator. */ public List optimizations; /** * Specifies the number of optimization passes. */ public int optimizationPasses = 1; /** * A list of {@link ClassSpecification} instances, whose methods are * assumed to have no side effects. */ public List assumeNoSideEffects; /** * A list of {@link ClassSpecification} instances, whose methods are * assumed to have no side external effects (that is, outside of 'this'). */ public List assumeNoExternalSideEffects; /** * A list of {@link ClassSpecification} instances, whose methods are * assumed not to let any reference parameters escape (including 'this'). */ public List assumeNoEscapingParameters; /** * A list of {@link ClassSpecification} instances, whose methods are * assumed not to return any external references (only parameters and new * instances). */ public List assumeNoExternalReturnValues; /** * A list of {@link ClassSpecification} instances, with fields and methods * that have specified fixed primitive values. */ public List assumeValues; /** * Specifies whether the access of class members can be modified. */ public boolean allowAccessModification = false; /** * Specifies whether interfaces may be merged aggressively. */ public boolean mergeInterfacesAggressively = false; /////////////////////////////////////////////////////////////////////////// // Obfuscation options. /////////////////////////////////////////////////////////////////////////// /** * Specifies whether the code should be obfuscated. */ public boolean obfuscate = true; /** * An optional output file for listing the obfuscation mapping. * An empty file name means the standard output. */ public File printMapping; /** * An optional input file for reading an obfuscation mapping. */ public File applyMapping; /** * An optional name of a file containing obfuscated class member names. */ public URL obfuscationDictionary; /** * An optional name of a file containing obfuscated class names. */ public URL classObfuscationDictionary; /** * An optional name of a file containing obfuscated package names. */ public URL packageObfuscationDictionary; /** * Specifies whether to apply aggressive name overloading on class members. */ public boolean overloadAggressively = false; /** * Specifies whether to generate globally unique class member names. */ public boolean useUniqueClassMemberNames = false; /** * Specifies whether obfuscated packages and classes can get mixed-case names. */ public boolean useMixedCaseClassNames = true; /** * A list of String instances specifying package names to be kept. * A null list means no names. An empty list means all * names. The package names may contain "**", "*", or "?" wildcards, and * they may be preceded by the "!" negator. */ public List keepPackageNames; /** * An optional base package if the obfuscated package hierarchy is to be * flattened, null otherwise. */ public String flattenPackageHierarchy; /** * An optional base package if the obfuscated classes are to be repackaged * into a single package, null otherwise. */ public String repackageClasses; /** * A list of String instances specifying optional attributes to be kept. * A null list means no attributes. An empty list means all * attributes. The attribute names may contain "*" or "?" wildcards, and * they may be preceded by the "!" negator. */ public List keepAttributes; /** * Specifies whether method parameter names and types should be kept for * methods that are not obfuscated. This is achieved by keeping partial * "LocalVariableTable" and "LocalVariableTypeTable" attributes. */ public boolean keepParameterNames = false; /** * An optional replacement for all SourceFile attributes. */ public String newSourceFileAttribute; /** * A list of String instances specifying a filter for classes whose * string constants are to be adapted, based on corresponding obfuscated * class names. */ public List adaptClassStrings; /** * A list of String instances specifying a filter for files whose * names are to be adapted, based on corresponding obfuscated class names. */ public List adaptResourceFileNames; /** * A list of String instances specifying a filter for files whose * contents are to be adapted, based on obfuscated class names. */ public List adaptResourceFileContents; /////////////////////////////////////////////////////////////////////////// // Preverification options. /////////////////////////////////////////////////////////////////////////// /** * Specifies whether the code should be preverified. */ public boolean preverify = true; /** * Specifies whether the code should be preverified for Java Micro Edition * (creating StackMap attributes) instead of for Java Standard Edition * (creating StackMapTable attributes). */ public boolean microEdition = false; /** * Specifies whether the code should be targeted at the Android platform. */ public boolean android = false; /////////////////////////////////////////////////////////////////////////// // Jar signing options. /////////////////////////////////////////////////////////////////////////// /** * A list of File instances specifying the key stores to be used when * signing jars. */ public List keyStores; /** * A list of String instances specifying the passwords of the key stores * to be used when signing jars. */ public List keyStorePasswords; /** * A list of String instances specifying the names of the keys to be used * when signing jars. */ public List keyAliases; /** * A list of String instances specifying the passwords of the keys to be * used when signing jars. */ public List keyPasswords; /////////////////////////////////////////////////////////////////////////// // General options. /////////////////////////////////////////////////////////////////////////// /** * Specifies whether to print verbose messages. */ public boolean verbose = false; /** * A list of String instances specifying a filter for the classes for * which not to print notes, if there are noteworthy potential problems. * A null list means all classes. The class names may contain * "**", "*", or "?" wildcards, and they may be preceded by the "!" negator. */ public List note = null; /** * A list of String instances specifying a filter for the classes for * which not to print warnings, if there are any problems. * A null list means all classes. The class names may contain * "**", "*", or "?" wildcards, and they may be preceded by the "!" negator. */ public List warn = null; /** * Specifies whether to ignore any warnings. */ public boolean ignoreWarnings = false; /** * An optional output file for printing out the configuration that ProGuard * is using (with included files and replaced variables). * An empty file name means the standard output. */ public File printConfiguration; /** * An optional output file for printing out the processed code in a more * or less readable form. An empty file name means the standard output. */ public File dump; /** * Specifies whether to add logging to reflection code, providing suggestions * on the ProGuard configuration. */ public boolean addConfigurationDebugging; /** * Specifies whether to backporting of class files to another * targetClassVersion shall be enabled. */ public boolean backport = false; /** * Specifies whether to process Kotlin metadata or not. */ public boolean keepKotlinMetadata = false; /** * Specifies not to process Kotlin metadata. Overwrites KeepKotlinMetadata. */ public boolean dontProcessKotlinMetadata = false; // INTERNAL OPTIONS /** * Enables of disables the Kotlin asserter. This is a hidden option, * not available via the configuration file. */ public boolean enableKotlinAsserter = true; /** * File to write extra data entries to; instead of writing them to * their respective jars. See {@link proguard.io.ExtraDataEntryNameMap}. */ public File extraJar; /** * Specifies whether conservative optimization should be applied */ public boolean optimizeConservatively = true; } ================================================ FILE: base/src/main/java/proguard/ConfigurationConstants.java ================================================ /* * 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; /** * This class provides constants for parsing and writing ProGuard configurations. * * @author Eric Lafortune */ public class ConfigurationConstants { public static final String OPTION_PREFIX = "-"; public static final String AT_DIRECTIVE = "@"; public static final String INCLUDE_DIRECTIVE = "-include"; public static final String BASE_DIRECTORY_DIRECTIVE = "-basedirectory"; public static final String INJARS_OPTION = "-injars"; public static final String OUTJARS_OPTION = "-outjars"; public static final String LIBRARYJARS_OPTION = "-libraryjars"; public static final String RESOURCEJARS_OPTION = "-resourcejars"; public static final String IF_OPTION = "-if"; public static final String KEEP_OPTION = "-keep"; public static final String KEEP_CLASS_MEMBERS_OPTION = "-keepclassmembers"; public static final String KEEP_CLASSES_WITH_MEMBERS_OPTION = "-keepclasseswithmembers"; public static final String KEEP_NAMES_OPTION = "-keepnames"; public static final String KEEP_CLASS_MEMBER_NAMES_OPTION = "-keepclassmembernames"; public static final String KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION = "-keepclasseswithmembernames"; public static final String KEEP_CODE_OPTION = "-keepcode"; public static final String INCLUDE_DESCRIPTOR_CLASSES_SUBOPTION = "includedescriptorclasses"; public static final String INCLUDE_CODE_SUBOPTION = "includecode"; public static final String ALLOW_SHRINKING_SUBOPTION = "allowshrinking"; public static final String ALLOW_OPTIMIZATION_SUBOPTION = "allowoptimization"; public static final String ALLOW_OBFUSCATION_SUBOPTION = "allowobfuscation"; public static final String PRINT_SEEDS_OPTION = "-printseeds"; public static final String DONT_SHRINK_OPTION = "-dontshrink"; public static final String PRINT_USAGE_OPTION = "-printusage"; public static final String WHY_ARE_YOU_KEEPING_OPTION = "-whyareyoukeeping"; public static final String DONT_OPTIMIZE_OPTION = "-dontoptimize"; public static final String OPTIMIZATIONS = "-optimizations"; public static final String OPTIMIZATION_PASSES = "-optimizationpasses"; public static final String ASSUME_NO_SIDE_EFFECTS_OPTION = "-assumenosideeffects"; public static final String ASSUME_NO_EXTERNAL_SIDE_EFFECTS_OPTION = "-assumenoexternalsideeffects"; public static final String ASSUME_NO_ESCAPING_PARAMETERS_OPTION = "-assumenoescapingparameters"; public static final String ASSUME_NO_EXTERNAL_RETURN_VALUES_OPTION = "-assumenoexternalreturnvalues"; public static final String ASSUME_VALUES_OPTION = "-assumevalues"; public static final String ALLOW_ACCESS_MODIFICATION_OPTION = "-allowaccessmodification"; public static final String MERGE_INTERFACES_AGGRESSIVELY_OPTION = "-mergeinterfacesaggressively"; public static final String DONT_OBFUSCATE_OPTION = "-dontobfuscate"; public static final String PRINT_MAPPING_OPTION = "-printmapping"; public static final String APPLY_MAPPING_OPTION = "-applymapping"; public static final String OBFUSCATION_DICTIONARY_OPTION = "-obfuscationdictionary"; public static final String CLASS_OBFUSCATION_DICTIONARY_OPTION = "-classobfuscationdictionary"; public static final String PACKAGE_OBFUSCATION_DICTIONARY_OPTION = "-packageobfuscationdictionary"; public static final String OVERLOAD_AGGRESSIVELY_OPTION = "-overloadaggressively"; public static final String USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION = "-useuniqueclassmembernames"; public static final String DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION = "-dontusemixedcaseclassnames"; public static final String KEEP_PACKAGE_NAMES_OPTION = "-keeppackagenames"; public static final String FLATTEN_PACKAGE_HIERARCHY_OPTION = "-flattenpackagehierarchy"; public static final String REPACKAGE_CLASSES_OPTION = "-repackageclasses"; public static final String DEFAULT_PACKAGE_OPTION = "-defaultpackage"; public static final String KEEP_ATTRIBUTES_OPTION = "-keepattributes"; public static final String KEEP_PARAMETER_NAMES_OPTION = "-keepparameternames"; public static final String RENAME_SOURCE_FILE_ATTRIBUTE_OPTION = "-renamesourcefileattribute"; public static final String ADAPT_CLASS_STRINGS_OPTION = "-adaptclassstrings"; public static final String ADAPT_RESOURCE_FILE_NAMES_OPTION = "-adaptresourcefilenames"; public static final String ADAPT_RESOURCE_FILE_CONTENTS_OPTION = "-adaptresourcefilecontents"; public static final String DONT_PREVERIFY_OPTION = "-dontpreverify"; public static final String MICRO_EDITION_OPTION = "-microedition"; public static final String ANDROID_OPTION = "-android"; public static final String KEY_STORE_OPTION = "-keystore"; public static final String KEY_STORE_PASSWORD_OPTION = "-keystorepassword"; public static final String KEY_ALIAS_OPTION = "-keyalias"; public static final String KEY_PASSWORD_OPTION = "-keypassword"; public static final String VERBOSE_OPTION = "-verbose"; public static final String DONT_NOTE_OPTION = "-dontnote"; public static final String DONT_WARN_OPTION = "-dontwarn"; public static final String IGNORE_WARNINGS_OPTION = "-ignorewarnings"; public static final String PRINT_CONFIGURATION_OPTION = "-printconfiguration"; public static final String DUMP_OPTION = "-dump"; public static final String ADD_CONFIGURATION_DEBUGGING_OPTION = "-addconfigurationdebugging"; public static final String SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION = "-skipnonpubliclibraryclasses"; public static final String DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION = "-dontskipnonpubliclibraryclasses"; public static final String DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION = "-dontskipnonpubliclibraryclassmembers"; public static final String TARGET_OPTION = "-target"; public static final String KEEP_DIRECTORIES_OPTION = "-keepdirectories"; public static final String DONT_COMPRESS_OPTION = "-dontcompress"; public static final String ZIP_ALIGN_OPTION = "-zipalign"; public static final String FORCE_PROCESSING_OPTION = "-forceprocessing"; public static final String KEEP_KOTLIN_METADATA = "-keepkotlinmetadata"; public static final String DONT_PROCESS_KOTLIN_METADATA = "-dontprocesskotlinmetadata"; public static final String OPTIMIZE_AGGRESSIVELY = "-optimizeaggressively"; public static final String ANY_FILE_KEYWORD = "**"; public static final String ANY_ATTRIBUTE_KEYWORD = "*"; public static final String ATTRIBUTE_SEPARATOR_KEYWORD = ","; public static final String JAR_SEPARATOR_KEYWORD = System.getProperty("path.separator"); public static final char OPEN_SYSTEM_PROPERTY = '<'; public static final char CLOSE_SYSTEM_PROPERTY = '>'; public static final String ANNOTATION_KEYWORD = "@"; public static final String NEGATOR_KEYWORD = "!"; public static final String CLASS_KEYWORD = "class"; public static final String ANY_CLASS_KEYWORD = "*"; public static final String ANY_TYPE_KEYWORD = "***"; public static final String IMPLEMENTS_KEYWORD = "implements"; public static final String EXTENDS_KEYWORD = "extends"; public static final String OPEN_KEYWORD = "{"; public static final String ANY_CLASS_MEMBER_KEYWORD = "*"; public static final String ANY_FIELD_KEYWORD = ""; public static final String ANY_METHOD_KEYWORD = ""; public static final String OPEN_ARGUMENTS_KEYWORD = "("; public static final String ARGUMENT_SEPARATOR_KEYWORD = ","; public static final String ANY_ARGUMENTS_KEYWORD = "..."; public static final String CLOSE_ARGUMENTS_KEYWORD = ")"; public static final String EQUAL_KEYWORD = "="; public static final String RETURN_KEYWORD = "return"; public static final String FALSE_KEYWORD = "false"; public static final String TRUE_KEYWORD = "true"; public static final String RANGE_KEYWORD = ".."; public static final String SEPARATOR_KEYWORD = ";"; public static final String CLOSE_KEYWORD = "}"; } ================================================ FILE: base/src/main/java/proguard/ConfigurationParser.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import proguard.util.*; import java.io.*; import java.net.*; import java.util.*; /** * This class parses ProGuard configurations. Configurations can be read from an * array of arguments or from a configuration file or URL. External references * in file names ('<...>') can be resolved against a given set of properties. * * @author Eric Lafortune */ public class ConfigurationParser implements AutoCloseable { protected final WordReader reader; protected final Properties properties; protected String nextWord; protected String lastComments; /** * Creates a new ConfigurationParser for the given String arguments and * the given Properties. */ public ConfigurationParser(String[] args, Properties properties) throws IOException { this(args, null, properties); } /** * Creates a new ConfigurationParser for the given String arguments, * with the given base directory and the given Properties. */ public ConfigurationParser(String[] args, File baseDir, Properties properties) throws IOException { this(new ArgumentWordReader(args, baseDir), properties); } /** * Creates a new ConfigurationParser for the given lines, * with the given base directory and the given Properties. */ public ConfigurationParser(String lines, String description, File baseDir, Properties properties) throws IOException { this(new LineWordReader(new LineNumberReader(new StringReader(lines)), description, baseDir), properties); } /** * Creates a new ConfigurationParser for the given file, with the system * Properties. * @deprecated Temporary code for backward compatibility in Obclipse. */ public ConfigurationParser(File file) throws IOException { this(file, System.getProperties()); } /** * Creates a new ConfigurationParser for the given file and the given * Properties. */ public ConfigurationParser(File file, Properties properties) throws IOException { this(new FileWordReader(file), properties); } /** * Creates a new ConfigurationParser for the given URL and the given * Properties. */ public ConfigurationParser(URL url, Properties properties) throws IOException { this(new FileWordReader(url), properties); } /** * Creates a new ConfigurationParser for the given word reader and the * given Properties. */ public ConfigurationParser(WordReader reader, Properties properties) throws IOException { this.reader = reader; this.properties = properties; readNextWord(); } /** * Parses and returns the configuration. * @param configuration the configuration that is updated as a side-effect. * @throws ParseException if the any of the configuration settings contains * a syntax error. * @throws IOException if an IO error occurs while reading a configuration. */ public void parse(Configuration configuration) throws ParseException, IOException { while (nextWord != null) { lastComments = reader.lastComments(); // First include directives. if (ConfigurationConstants.AT_DIRECTIVE .startsWith(nextWord) || ConfigurationConstants.INCLUDE_DIRECTIVE .startsWith(nextWord)) configuration.lastModified = parseIncludeArgument(configuration.lastModified); else if (ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE .startsWith(nextWord)) parseBaseDirectoryArgument(); // Then configuration options with or without arguments. else if (ConfigurationConstants.INJARS_OPTION .startsWith(nextWord)) configuration.programJars = parseClassPathArgument(configuration.programJars, false, true); else if (ConfigurationConstants.OUTJARS_OPTION .startsWith(nextWord)) configuration.programJars = parseClassPathArgument(configuration.programJars, true, false); else if (ConfigurationConstants.LIBRARYJARS_OPTION .startsWith(nextWord)) configuration.libraryJars = parseClassPathArgument(configuration.libraryJars, false, false); else if (ConfigurationConstants.RESOURCEJARS_OPTION .startsWith(nextWord)) throw new ParseException("The '-resourcejars' option is no longer supported. Please use the '-injars' option for all input"); else if (ConfigurationConstants.SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION .startsWith(nextWord)) configuration.skipNonPublicLibraryClasses = parseNoArgument(true); else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION .startsWith(nextWord)) configuration.skipNonPublicLibraryClasses = parseNoArgument(false); else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION.startsWith(nextWord)) configuration.skipNonPublicLibraryClassMembers = parseNoArgument(false); else if (ConfigurationConstants.TARGET_OPTION .startsWith(nextWord)) configuration.targetClassVersion = parseClassVersion(); else if (ConfigurationConstants.DONT_COMPRESS_OPTION .startsWith(nextWord)) configuration.dontCompress = parseCommaSeparatedList("file name", true, true, false, true, false, true, false, false, false, configuration.dontCompress); else if (ConfigurationConstants.ZIP_ALIGN_OPTION .startsWith(nextWord)) configuration.zipAlign = parseIntegerArgument(); else if (ConfigurationConstants.FORCE_PROCESSING_OPTION .startsWith(nextWord)) configuration.lastModified = parseNoArgument(Long.MAX_VALUE); else if (ConfigurationConstants.IF_OPTION .startsWith(nextWord)) configuration.keep = parseIfCondition(configuration.keep); else if (ConfigurationConstants.KEEP_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, true, true, false, false, false, null); else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, true, false, false, false, null); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, true, false, true, false, null); else if (ConfigurationConstants.KEEP_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, true, true, false, false, true, null); else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, true, false, false, true, null); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, true, false, true, true, null); else if (ConfigurationConstants.KEEP_CODE_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, false, true, false, false, null); else if (ConfigurationConstants.PRINT_SEEDS_OPTION .startsWith(nextWord)) configuration.printSeeds = parseOptionalFile(); // After '-keep'. else if (ConfigurationConstants.KEEP_DIRECTORIES_OPTION .startsWith(nextWord)) configuration.keepDirectories = parseCommaSeparatedList("directory name", true, true, false, true, false, true, true, false, false, configuration.keepDirectories); else if (ConfigurationConstants.DONT_SHRINK_OPTION .startsWith(nextWord)) configuration.shrink = parseNoArgument(false); else if (ConfigurationConstants.PRINT_USAGE_OPTION .startsWith(nextWord)) configuration.printUsage = parseOptionalFile(); else if (ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION .startsWith(nextWord)) configuration.whyAreYouKeeping = parseClassSpecificationArguments(configuration.whyAreYouKeeping); else if (ConfigurationConstants.DONT_OPTIMIZE_OPTION .startsWith(nextWord)) configuration.optimize = parseNoArgument(false); else if (ConfigurationConstants.OPTIMIZATION_PASSES .startsWith(nextWord)) configuration.optimizationPasses = parseIntegerArgument(); else if (ConfigurationConstants.OPTIMIZATIONS .startsWith(nextWord)) configuration.optimizations = parseCommaSeparatedList("optimization name", true, false, false, false, false, true, false, false, false, configuration.optimizations); else if (ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION .startsWith(nextWord)) configuration.assumeNoSideEffects = parseAssumeClassSpecificationArguments(configuration.assumeNoSideEffects); else if (ConfigurationConstants.ASSUME_NO_EXTERNAL_SIDE_EFFECTS_OPTION .startsWith(nextWord)) configuration.assumeNoExternalSideEffects = parseAssumeClassSpecificationArguments(configuration.assumeNoExternalSideEffects); else if (ConfigurationConstants.ASSUME_NO_ESCAPING_PARAMETERS_OPTION .startsWith(nextWord)) configuration.assumeNoEscapingParameters = parseAssumeClassSpecificationArguments(configuration.assumeNoEscapingParameters); else if (ConfigurationConstants.ASSUME_NO_EXTERNAL_RETURN_VALUES_OPTION .startsWith(nextWord)) configuration.assumeNoExternalReturnValues = parseAssumeClassSpecificationArguments(configuration.assumeNoExternalReturnValues); else if (ConfigurationConstants.ASSUME_VALUES_OPTION .startsWith(nextWord)) configuration.assumeValues = parseAssumeClassSpecificationArguments(configuration.assumeValues); else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION .startsWith(nextWord)) configuration.allowAccessModification = parseNoArgument(true); else if (ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.mergeInterfacesAggressively = parseNoArgument(true); else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION .startsWith(nextWord)) configuration.obfuscate = parseNoArgument(false); else if (ConfigurationConstants.PRINT_MAPPING_OPTION .startsWith(nextWord)) configuration.printMapping = parseOptionalFile(); else if (ConfigurationConstants.APPLY_MAPPING_OPTION .startsWith(nextWord)) configuration.applyMapping = parseFile(); else if (ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.obfuscationDictionary = parseURL(); else if (ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.classObfuscationDictionary = parseURL(); else if (ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.packageObfuscationDictionary = parseURL(); else if (ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.overloadAggressively = parseNoArgument(true); else if (ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.useUniqueClassMemberNames = parseNoArgument(true); else if (ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION .startsWith(nextWord)) configuration.useMixedCaseClassNames = parseNoArgument(false); else if (ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION .startsWith(nextWord)) configuration.keepPackageNames = parseCommaSeparatedList("package name", true, true, false, false, true, false, false, true, false, configuration.keepPackageNames); else if (ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION .startsWith(nextWord)) configuration.flattenPackageHierarchy = ClassUtil.internalClassName(parseOptionalArgument()); else if (ConfigurationConstants.REPACKAGE_CLASSES_OPTION .startsWith(nextWord) || ConfigurationConstants.DEFAULT_PACKAGE_OPTION .startsWith(nextWord)) configuration.repackageClasses = ClassUtil.internalClassName(parseOptionalArgument()); else if (ConfigurationConstants.KEEP_ATTRIBUTES_OPTION .startsWith(nextWord)) configuration.keepAttributes = parseCommaSeparatedList("attribute name", true, true, false, false, true, false, false, false, false, configuration.keepAttributes); else if (ConfigurationConstants.KEEP_PARAMETER_NAMES_OPTION .startsWith(nextWord)) configuration.keepParameterNames = parseNoArgument(true); else if (ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION .startsWith(nextWord)) configuration.newSourceFileAttribute = parseOptionalArgument(); else if (ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION .startsWith(nextWord)) configuration.adaptClassStrings = parseCommaSeparatedList("class name", true, true, false, false, true, false, false, true, false, configuration.adaptClassStrings); else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION .startsWith(nextWord)) configuration.adaptResourceFileNames = parseCommaSeparatedList("resource file name", true, true, false, true, false, true, false, false, false, configuration.adaptResourceFileNames); else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION .startsWith(nextWord)) configuration.adaptResourceFileContents = parseCommaSeparatedList("resource file name", true, true, false, true, false, true, false, false, false, configuration.adaptResourceFileContents); else if (ConfigurationConstants.DONT_PROCESS_KOTLIN_METADATA .startsWith(nextWord)) configuration.dontProcessKotlinMetadata = parseNoArgument(true); else if (ConfigurationConstants.KEEP_KOTLIN_METADATA .startsWith(nextWord)) configuration.keepKotlinMetadata = parseKeepKotlinMetadata(); else if (ConfigurationConstants.DONT_PREVERIFY_OPTION .startsWith(nextWord)) configuration.preverify = parseNoArgument(false); else if (ConfigurationConstants.MICRO_EDITION_OPTION .startsWith(nextWord)) configuration.microEdition = parseNoArgument(true); else if (ConfigurationConstants.ANDROID_OPTION .startsWith(nextWord)) configuration.android = parseNoArgument(true); else if (ConfigurationConstants.KEY_STORE_OPTION .startsWith(nextWord)) configuration.keyStores = parseFiles(configuration.keyStores); else if (ConfigurationConstants.KEY_STORE_PASSWORD_OPTION .startsWith(nextWord)) configuration.keyStorePasswords = parseCommaSeparatedList("keystore password", true, false, false, false, false, false, true, false, false, configuration.keyStorePasswords); else if (ConfigurationConstants.KEY_ALIAS_OPTION .startsWith(nextWord)) configuration.keyAliases = parseCommaSeparatedList("key", true, false, false, false, false, false, true, false, false, configuration.keyAliases); else if (ConfigurationConstants.KEY_PASSWORD_OPTION .startsWith(nextWord)) configuration.keyPasswords = parseCommaSeparatedList("key password", true, false, false, false, false, false, true, false, false, configuration.keyPasswords); else if (ConfigurationConstants.VERBOSE_OPTION .startsWith(nextWord)) configuration.verbose = parseNoArgument(true); else if (ConfigurationConstants.DONT_NOTE_OPTION .startsWith(nextWord)) configuration.note = parseCommaSeparatedList("class name", true, true, false, false, true, false, false, true, false, configuration.note); else if (ConfigurationConstants.DONT_WARN_OPTION .startsWith(nextWord)) configuration.warn = parseCommaSeparatedList("class name", true, true, false, false, true, false, false, true, false, configuration.warn); else if (ConfigurationConstants.IGNORE_WARNINGS_OPTION .startsWith(nextWord)) configuration.ignoreWarnings = parseNoArgument(true); else if (ConfigurationConstants.PRINT_CONFIGURATION_OPTION .startsWith(nextWord)) configuration.printConfiguration = parseOptionalFile(); else if (ConfigurationConstants.DUMP_OPTION .startsWith(nextWord)) configuration.dump = parseOptionalFile(); else if (ConfigurationConstants.ADD_CONFIGURATION_DEBUGGING_OPTION .startsWith(nextWord)) configuration.addConfigurationDebugging = parseNoArgument(true); else if (ConfigurationConstants.OPTIMIZE_AGGRESSIVELY .startsWith(nextWord)) configuration.optimizeConservatively = parseNoArgument(false); else { throw new ParseException("Unknown option " + reader.locationDescription()); } } } /** * Closes the configuration. * @throws IOException if an IO error occurs while closing the configuration. */ @Override public void close() throws IOException { if (reader != null) { reader.close(); } } protected boolean parseKeepKotlinMetadata() throws IOException { System.err.println("The `-keepkotlinmetadata` option is deprecated and will be removed in a future ProGuard release." + "Please use `-keep class kotlin.Metadata` instead."); return parseNoArgument(true); } protected long parseIncludeArgument(long lastModified) throws ParseException, IOException { // Read the configuration file name. readNextWord("configuration file name", true, true, false); URL baseURL = reader.getBaseURL(); URL url = null; try { // Check if the file name is a valid URL. url = new URL(nextWord); } catch (MalformedURLException ex) {} if (url != null) { reader.includeWordReader(new FileWordReader(url)); } // Is it relative to a URL or to a file? else if (baseURL != null) { url = new URL(baseURL, nextWord); reader.includeWordReader(new FileWordReader(url)); } else { // Is the file a valid resource URL? url = ConfigurationParser.class.getResource(nextWord); if (url != null) { reader.includeWordReader(new FileWordReader(url)); } else { File file = file(nextWord); reader.includeWordReader(new FileWordReader(file)); long fileLastModified = file.lastModified(); if (fileLastModified > lastModified) { lastModified = fileLastModified; } } } readNextWord(); return lastModified; } protected void parseBaseDirectoryArgument() throws ParseException, IOException { // Read the base directory name. readNextWord("base directory name", true, true, false); reader.setBaseDir(file(nextWord)); readNextWord(); } protected ClassPath parseClassPathArgument(ClassPath classPath, boolean isOutput, boolean allowFeatureName) throws ParseException, IOException { // Create a new List if necessary. if (classPath == null) { classPath = new ClassPath(); } // Read the first jar name. readNextWord("jar or directory name", true, false, false); // Do we have a suboption with a feature name instead? // The file name then comes out as an empty string. String featureName = null; if (allowFeatureName && nextWord.length() == 0) { // Read the separator. readNextWord("separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + "'"); if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + "' or jar or directory name" + "' before " + reader.locationDescription()); } // Read and remember the feature name. readNextWord("feature name", false, false, false); featureName = nextWord; // Read the first jar name. readNextWord("jar or directory name", true, false, false); } while (true) { // Create a new class path entry. ClassPathEntry entry = new ClassPathEntry(file(nextWord), isOutput, featureName); // Read the opening parenthesis or the separator, if any. readNextWord(); // Read the optional filters. if (!configurationEnd() && ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord)) { // Read all filters in an array. List[] filters = new List[7]; int counter = 0; do { // Read the filter. filters[counter++] = parseCommaSeparatedList("filter", true, true, true, true, false, true, true, false, false, null); } while (counter < filters.length && ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)); // Make sure there is a closing parenthesis. if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + "' or '" + ConfigurationConstants.SEPARATOR_KEYWORD + "', or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + "' before " + reader.locationDescription()); } // Set all filters from the array on the entry. entry.setFilter(filters[--counter]); if (counter > 0) { entry.setJarFilter(filters[--counter]); if (counter > 0) { entry.setWarFilter(filters[--counter]); if (counter > 0) { entry.setEarFilter(filters[--counter]); if (counter > 0) { entry.setJmodFilter(filters[--counter]); if (counter > 0) { entry.setZipFilter(filters[--counter]); if (counter > 0) { // For backward compatibility, the // jmod/aar/aab/apk filters come first // in the list. entry.setApkFilter(filters[--counter]); if (counter > 0) { entry.setAabFilter(filters[--counter]); if (counter > 0) { entry.setAarFilter(filters[--counter]); if (counter > 0) { entry.setJmodFilter(filters[--counter]); } } } } } } } } } // Read the separator, if any. readNextWord(); } // Add the entry to the list. classPath.add(entry); if (configurationEnd()) { return classPath; } if (!nextWord.equals(ConfigurationConstants.JAR_SEPARATOR_KEYWORD)) { throw new ParseException("Expecting class path separator '" + ConfigurationConstants.JAR_SEPARATOR_KEYWORD + "' before " + reader.locationDescription()); } // Read the next jar name. readNextWord("jar or directory name", true, false, false); } } protected int parseClassVersion() throws ParseException, IOException { // Read the obligatory target. readNextWord("java version"); int classVersion = ClassUtil.internalClassVersion(nextWord); if (classVersion == 0) { throw new ParseException("Unsupported java version " + reader.locationDescription()); } readNextWord(); return classVersion; } protected int parseIntegerArgument() throws ParseException, IOException { try { // Read the obligatory integer. readNextWord("integer"); int integer = Integer.parseInt(nextWord); if (integer <= 0) { throw new ParseException("Expecting value larger than 0, instead of '" + nextWord + "' before " + reader.locationDescription()); } readNextWord(); return integer; } catch (NumberFormatException e) { throw new ParseException("Expecting integer argument instead of '" + nextWord + "' before " + reader.locationDescription()); } } protected URL parseURL() throws ParseException, IOException { // Read the obligatory file name. readNextWord("file name", true, true, false); // Make sure the file is properly resolved. URL url = url(nextWord); readNextWord(); return url; } protected List parseFiles(List files) throws ParseException, IOException { if (files == null) { files = new ArrayList(); } files.add(parseFile()); return files; } protected File parseFile() throws ParseException, IOException { // Read the obligatory file name. readNextWord("file name", true, true, false); // Make sure the file is properly resolved. File file = file(nextWord); readNextWord(); return file; } protected File parseOptionalFile() throws ParseException, IOException { // Read the optional file name. readNextWord(true, true); // Didn't the user specify a file name? if (configurationEnd()) { return Configuration.STD_OUT; } // Make sure the file is properly resolved. File file = file(nextWord); readNextWord(); return file; } protected String parseOptionalArgument() throws IOException { // Read the optional argument. readNextWord(); // Didn't the user specify an argument? if (configurationEnd()) { return ""; } String argument = nextWord; readNextWord(); return argument; } protected boolean parseNoArgument(boolean value) throws IOException { readNextWord(); return value; } protected long parseNoArgument(long value) throws IOException { readNextWord(); return value; } /** * Parses and adds a conditional class specification to keep other classes * and class members. * For example: -if "public class SomeClass { void someMethod(); } -keep" * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ protected List parseIfCondition(List keepClassSpecifications) throws ParseException, IOException { // Read the condition. ClassSpecification condition = parseClassSpecificationArguments(true, true, false); // Read the corresponding keep option. if (nextWord == null) { throw new ParseException("Expecting '-keep' option after '-if' option, before " + reader.locationDescription()); } if (ConfigurationConstants.KEEP_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, true, true, false, false, false, condition); else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, false, true, false, false, false, condition); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, false, true, false, true, false, condition); else if (ConfigurationConstants.KEEP_NAMES_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, true, true, false, false, true, condition); else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, false, true, false, false, true, condition); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION.startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, false, true, false, true, true, condition); else if (ConfigurationConstants.KEEP_CODE_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, false, false, true, false, false, condition); else { throw new ParseException("Expecting '-keep' option after '-if' option, before " + reader.locationDescription()); } return keepClassSpecifications; } /** * Parses and adds a class specification to keep classes and class members. * For example: -keep "public class SomeClass { void someMethod(); }" * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ protected List parseKeepClassSpecificationArguments(List keepClassSpecifications, boolean markClasses, boolean markMembers, boolean markCodeAttributes, boolean markConditionally, boolean allowShrinking, ClassSpecification condition) throws ParseException, IOException { // Create a new List if necessary. if (keepClassSpecifications == null) { keepClassSpecifications = new ArrayList(); } // Read and add the keep configuration. keepClassSpecifications.add(parseKeepClassSpecificationArguments(markClasses, markMembers, markCodeAttributes, markConditionally, allowShrinking, condition)); return keepClassSpecifications; } /** * Parses and returns a class specification to keep classes and class * members. * For example: -keep "public class SomeClass { void someMethod(); }" * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ protected KeepClassSpecification parseKeepClassSpecificationArguments(boolean markClasses, boolean markMembers, boolean markCodeAttributes, boolean markConditionally, boolean allowShrinking, ClassSpecification condition) throws ParseException, IOException { boolean markDescriptorClasses = false; //boolean allowShrinking = false; boolean allowOptimization = false; boolean allowObfuscation = false; // Read the keep modifiers. while (true) { readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD + "', '" + JavaAccessConstants.INTERFACE + "', or '" + JavaAccessConstants.ENUM + "'", false, false, true); if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord)) { // Not a comma. Stop parsing the keep modifiers. break; } readNextWord("keyword '" + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION + "', '" + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION + "', or '" + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + "'"); if (ConfigurationConstants.INCLUDE_DESCRIPTOR_CLASSES_SUBOPTION.startsWith(nextWord)) { markDescriptorClasses = true; } else if (ConfigurationConstants.INCLUDE_CODE_SUBOPTION .startsWith(nextWord)) { markCodeAttributes = true; } else if (ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION .startsWith(nextWord)) { allowShrinking = true; } else if (ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION .startsWith(nextWord)) { allowOptimization = true; } else if (ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION .startsWith(nextWord)) { allowObfuscation = true; } else { throw new ParseException("Expecting keyword '" + ConfigurationConstants.INCLUDE_DESCRIPTOR_CLASSES_SUBOPTION + "', '" + ConfigurationConstants.INCLUDE_CODE_SUBOPTION + "', '" + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION + "', '" + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION + "', or '" + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + "' before " + reader.locationDescription()); } } // Read the class configuration. ClassSpecification classSpecification = parseClassSpecificationArguments(false, true, false); // Create and return the keep configuration. return new KeepClassSpecification(markClasses, markMembers, markConditionally, markDescriptorClasses, markCodeAttributes, allowShrinking, allowOptimization, allowObfuscation, condition, classSpecification); } /** * Parses and adds a class specification for optimization assumptions. * For example: -assumenosideeffects "public class Simple { void simple(); }" * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ protected List parseAssumeClassSpecificationArguments(List classSpecifications) throws ParseException, IOException { // Create a new List if necessary. if (classSpecifications == null) { classSpecifications = new ArrayList(); } // Read and add the class configuration. classSpecifications.add(parseClassSpecificationArguments(true, true, true)); return classSpecifications; } /** * Parses and adds a class specification. * For example: "class SomeClass { void someMethod(); }" * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ protected List parseClassSpecificationArguments(List classSpecifications) throws ParseException, IOException { // Create a new List if necessary. if (classSpecifications == null) { classSpecifications = new ArrayList(); } // Read and add the keep configuration. classSpecifications.add(parseClassSpecificationArguments(true, true, false)); return classSpecifications; } // Added for compatibility with older versions of ProGuard (see PGD-755). public ClassSpecification parseClassSpecificationArguments() throws ParseException, IOException { return parseClassSpecificationArguments(false, true, false); } /** * Parses and returns a class specification. * For example: "public class SomeClass { public void someMethod(); }" * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ public ClassSpecification parseClassSpecificationArguments(boolean readFirstWord, boolean allowClassMembers, boolean allowValues) throws ParseException, IOException { if (readFirstWord) { readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD + "', '" + JavaAccessConstants.INTERFACE + "', or '" + JavaAccessConstants.ENUM + "'", false, false, true); } // Clear the annotation type. String annotationType = null; // Clear the class access modifiers. int requiredSetClassAccessFlags = 0; int requiredUnsetClassAccessFlags = 0; // Parse the class annotations and access modifiers until the class keyword. while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord)) { // Strip the negating sign, if any. boolean negated = nextWord.startsWith(ConfigurationConstants.NEGATOR_KEYWORD); String strippedWord = negated ? nextWord.substring(1) : nextWord; // Parse the class access modifiers. int accessFlag = strippedWord.equals(JavaAccessConstants.PUBLIC) ? AccessConstants.PUBLIC : strippedWord.equals(JavaAccessConstants.FINAL) ? AccessConstants.FINAL : strippedWord.equals(JavaAccessConstants.INTERFACE) ? AccessConstants.INTERFACE : strippedWord.equals(JavaAccessConstants.ABSTRACT) ? AccessConstants.ABSTRACT : strippedWord.equals(JavaAccessConstants.SYNTHETIC) ? AccessConstants.SYNTHETIC : strippedWord.equals(JavaAccessConstants.ANNOTATION) ? AccessConstants.ANNOTATION : strippedWord.equals(JavaAccessConstants.ENUM) ? AccessConstants.ENUM : unknownAccessFlag(); // Is it an annotation modifier? if (accessFlag == AccessConstants.ANNOTATION) { readNextWord("annotation type or keyword '" + JavaAccessConstants.INTERFACE + "'", false, false, false); // Is the next word actually an annotation type? if (!nextWord.equals(JavaAccessConstants.INTERFACE) && !nextWord.equals(JavaAccessConstants.ENUM) && !nextWord.equals(ConfigurationConstants.CLASS_KEYWORD)) { // Parse the annotation type. annotationType = ListUtil.commaSeparatedString( parseCommaSeparatedList("annotation type", false, false, false, false, true, false, false, false, true, null), false); // Continue parsing the access modifier that we just read // in the next cycle. continue; } // Otherwise just handle the annotation modifier. } if (!negated) { requiredSetClassAccessFlags |= accessFlag; } else { requiredUnsetClassAccessFlags |= accessFlag; } if ((requiredSetClassAccessFlags & requiredUnsetClassAccessFlags) != 0) { throw new ParseException("Conflicting class access modifiers for '" + strippedWord + "' before " + reader.locationDescription()); } if (strippedWord.equals(JavaAccessConstants.INTERFACE) || strippedWord.equals(JavaAccessConstants.ENUM) || strippedWord.equals(ConfigurationConstants.CLASS_KEYWORD)) { // The interface or enum keyword. Stop parsing the class flags. break; } // Should we read the next word? if (accessFlag != AccessConstants.ANNOTATION) { readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD + "', '" + JavaAccessConstants.INTERFACE + "', or '" + JavaAccessConstants.ENUM + "'", false, false, true); } } // Parse the class name part. String externalClassName = ListUtil.commaSeparatedString( parseCommaSeparatedList("class name or interface name", true, false, false, false, true, false, false, false, false, null), false); // For backward compatibility, allow a single "*" wildcard to match any // class. String className = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalClassName) ? null : ClassUtil.internalClassName(externalClassName); // Clear the annotation type and the class name of the extends part. String extendsAnnotationType = null; String extendsClassName = null; if (allowClassMembers && !configurationEnd()) { // Parse 'implements ...' or 'extends ...' part, if any. if (ConfigurationConstants.IMPLEMENTS_KEYWORD.equals(nextWord) || ConfigurationConstants.EXTENDS_KEYWORD.equals(nextWord)) { readNextWord("class name or interface name", false, false, true); // Parse the annotation type, if any. if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord)) { extendsAnnotationType = ListUtil.commaSeparatedString( parseCommaSeparatedList("annotation type", true, false, false, false, true, false, false, false, true, null), false); } String externalExtendsClassName = ListUtil.commaSeparatedString( parseCommaSeparatedList("class name or interface name", false, false, false, false, true, false, false, false, false, null), false); extendsClassName = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalExtendsClassName) ? null : ClassUtil.internalClassName(externalExtendsClassName); } } // Create the basic class specification. ClassSpecification classSpecification = new ClassSpecification(lastComments, requiredSetClassAccessFlags, requiredUnsetClassAccessFlags, annotationType, className, extendsAnnotationType, extendsClassName); // Now add any class members to this class specification. if (allowClassMembers && !configurationEnd()) { // Check the class member opening part. if (!ConfigurationConstants.OPEN_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting opening '" + ConfigurationConstants.OPEN_KEYWORD + "' at " + reader.locationDescription()); } // Parse all class members. while (true) { readNextWord("class member description" + " or closing '" + ConfigurationConstants.CLOSE_KEYWORD + "'", false, false, true); if (nextWord.equals(ConfigurationConstants.CLOSE_KEYWORD)) { lastComments = reader.lastComments(); classSpecification.memberComments = lastComments; // The closing brace. Stop parsing the class members. readNextWord(); break; } parseMemberSpecificationArguments(externalClassName, allowValues, classSpecification); } } return classSpecification; } /** * Parses and adds a class member specification. * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ protected void parseMemberSpecificationArguments(String externalClassName, boolean allowValues, ClassSpecification classSpecification) throws ParseException, IOException { // Clear the annotation name. String annotationType = null; // Parse the class member access modifiers, if any. int requiredSetMemberAccessFlags = 0; int requiredUnsetMemberAccessFlags = 0; while (!configurationEnd(true)) { // Parse the annotation type, if any. if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord)) { annotationType = ListUtil.commaSeparatedString( parseCommaSeparatedList("annotation type", true, false, false, false, true, false, false, false, true, null), false); continue; } String strippedWord = nextWord.startsWith("!") ? nextWord.substring(1) : nextWord; // Parse the class member access modifiers. int accessFlag = strippedWord.equals(JavaAccessConstants.PUBLIC) ? AccessConstants.PUBLIC : strippedWord.equals(JavaAccessConstants.PRIVATE) ? AccessConstants.PRIVATE : strippedWord.equals(JavaAccessConstants.PROTECTED) ? AccessConstants.PROTECTED : strippedWord.equals(JavaAccessConstants.STATIC) ? AccessConstants.STATIC : strippedWord.equals(JavaAccessConstants.FINAL) ? AccessConstants.FINAL : strippedWord.equals(JavaAccessConstants.SYNCHRONIZED) ? AccessConstants.SYNCHRONIZED : strippedWord.equals(JavaAccessConstants.VOLATILE) ? AccessConstants.VOLATILE : strippedWord.equals(JavaAccessConstants.TRANSIENT) ? AccessConstants.TRANSIENT : strippedWord.equals(JavaAccessConstants.BRIDGE) ? AccessConstants.BRIDGE : strippedWord.equals(JavaAccessConstants.VARARGS) ? AccessConstants.VARARGS : strippedWord.equals(JavaAccessConstants.NATIVE) ? AccessConstants.NATIVE : strippedWord.equals(JavaAccessConstants.ABSTRACT) ? AccessConstants.ABSTRACT : strippedWord.equals(JavaAccessConstants.STRICT) ? AccessConstants.STRICT : strippedWord.equals(JavaAccessConstants.SYNTHETIC) ? AccessConstants.SYNTHETIC : 0; if (accessFlag == 0) { // Not a class member access modifier. Stop parsing them. break; } if (strippedWord.equals(nextWord)) { requiredSetMemberAccessFlags |= accessFlag; } else { requiredUnsetMemberAccessFlags |= accessFlag; } // Make sure the user doesn't try to set and unset the same // access flags simultaneously. if ((requiredSetMemberAccessFlags & requiredUnsetMemberAccessFlags) != 0) { throw new ParseException("Conflicting class member access modifiers for " + reader.locationDescription()); } readNextWord("class member description"); } // Parse the class member type and name part. // Did we get a special wildcard? if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord) || ConfigurationConstants.ANY_FIELD_KEYWORD .equals(nextWord) || ConfigurationConstants.ANY_METHOD_KEYWORD .equals(nextWord)) { // Act according to the type of wildcard. if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord)) { checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); checkMethodAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); classSpecification.addField( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); classSpecification.addMethod( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); } else if (ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord)) { checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); classSpecification.addField( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); } else if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord)) { checkMethodAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); classSpecification.addMethod( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); } // We still have to read the closing separator. readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'"); if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "' before " + reader.locationDescription()); } } else { // Make sure we have a proper type. checkJavaIdentifier("java type"); String type = nextWord; String typeLocation = reader.locationDescription(); readNextWord("class member name"); String name = nextWord; // Did we get just one word before the opening parenthesis? if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name)) { // This must be a constructor then. // Make sure the type is a proper constructor name. if (!(type.equals(ClassConstants.METHOD_NAME_INIT) || type.equals(externalClassName) || type.equals(ClassUtil.externalShortClassName(externalClassName)))) { throw new ParseException("Expecting type and name " + "instead of just '" + type + "' before " + reader.locationDescription()); } // Assign the fixed constructor type and name. type = JavaTypeConstants.VOID; name = ClassConstants.METHOD_NAME_INIT; } else { // It's not a constructor. // Make sure we have a proper name. checkJavaIdentifier("class member name"); // Read the opening parenthesis or the separating // semi-colon. readNextWord("opening '" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + "' or separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'"); } // Check if the type actually contains the use of generics. // Can not do it right away as we also support "" as a type (see case above). if (containsGenerics(type)) { throw new ParseException("Generics are not allowed (erased) for java type" + typeLocation); } // Are we looking at a field, a method, or something else? if (ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { // It's a field. checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); // We already have a field descriptor. String descriptor = ClassUtil.internalType(type); if (ConfigurationConstants.ANY_FIELD_KEYWORD.equals(name)) { throw new ParseException("Not expecting field type before with wildcard '" + ConfigurationConstants.ANY_FIELD_KEYWORD + "before " + reader.locationDescription() + " (use '" + ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD + "' instead)"); } // Add the field. classSpecification.addField( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, name, descriptor)); } else if (allowValues && (ConfigurationConstants.EQUAL_KEYWORD.equals(nextWord) || ConfigurationConstants.RETURN_KEYWORD.equals(nextWord))) { // It's a field with a value. checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); // We already have a field descriptor. String descriptor = ClassUtil.internalType(type); // Read the constant. Number[] values = parseValues(type, descriptor); // Read the separator after the constant. readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'"); if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "' before " + reader.locationDescription()); } // Add the field. classSpecification.addField( new MemberValueSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, name, descriptor, values)); } else if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord)) { // It's a method. checkMethodAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); // Parse the method arguments. String descriptor = ClassUtil.internalMethodDescriptor(type, parseCommaSeparatedList("argument", true, true, true, false, true, false, false, false, false, null)); if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + "' or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + "' before " + reader.locationDescription()); } // Read the separator after the closing parenthesis. readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'"); if (ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(name)) { throw new ParseException("Not expecting method descriptor with wildcard '" + ConfigurationConstants.ANY_METHOD_KEYWORD + "before " + reader.locationDescription() + " (use '" + ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD + "' instead)"); } // Add the plain method. classSpecification.addMethod( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, name, descriptor)); } else if (allowValues && (ConfigurationConstants.EQUAL_KEYWORD.equals(nextWord) || ConfigurationConstants.RETURN_KEYWORD.equals(nextWord))) { // It's a method with a value. checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); // Read the constant. Number[] values = parseValues(type, ClassUtil.internalType(type)); // Read the separator after the constant. readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'"); if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "' before " + reader.locationDescription()); } // Add the method. classSpecification.addMethod( new MemberValueSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, name, descriptor, values)); } else { throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "' before " + reader.locationDescription()); } } else { // It doesn't look like a field or a method. throw new ParseException("Expecting opening '" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + "' or separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "' before " + reader.locationDescription()); } } } /** * Reads a value or value range of the given primitive type. * For example, values "123" or "100..199" of type "int" ("I"). */ protected Number[] parseValues(String externalType, String internalType) throws ParseException, IOException { readNextWord(externalType + " constant"); int rangeIndex = nextWord.indexOf(ConfigurationConstants.RANGE_KEYWORD); return rangeIndex >= 0 ? new Number[] { parseValue(externalType, internalType, nextWord.substring(0, rangeIndex)), parseValue(externalType, internalType, nextWord.substring(rangeIndex + ConfigurationConstants.RANGE_KEYWORD.length())) } : new Number[] { parseValue(externalType, internalType, nextWord) }; } /** * Parses the given string as a value of the given primitive type. * For example, value "123" of type "int" ("I"). * For example, value "true" of type "boolean" ("Z"), returned as 1. */ protected Number parseValue(String externalType, String internalType, String string) throws ParseException { try { string = replaceSystemProperties(string); switch (internalType.charAt(0)) { case TypeConstants.BOOLEAN: { return parseBoolean(string); } case TypeConstants.BYTE: case TypeConstants.CHAR: case TypeConstants.SHORT: case TypeConstants.INT: { return Integer.decode(string); } //case TypeConstants.LONG: //{ // return Long.decode(string); //} //case TypeConstants.FLOAT: //{ // return Float.valueOf(string); //} //case TypeConstants.DOUBLE: //{ // return Double.valueOf(string); //} default: { throw new ParseException("Can't handle '" + externalType + "' constant " + reader.locationDescription()); } } } catch (NumberFormatException e) { throw new ParseException("Can't parse " + externalType + " constant " + reader.locationDescription()); } } /** * Parses the given boolean string as an integer (0 or 1). */ protected Integer parseBoolean(String string) throws ParseException { if (ConfigurationConstants.FALSE_KEYWORD.equals(nextWord)) { return Integer.valueOf(0); } else if (ConfigurationConstants.TRUE_KEYWORD.equals(nextWord)) { return Integer.valueOf(1); } else { throw new ParseException("Unknown boolean constant " + reader.locationDescription()); } } /** * Reads a comma-separated list of Lists of java identifiers or of file * names. */ protected List parseCommaSeparatedLists(String expectedDescription, boolean readFirstWord, boolean allowEmptyList, boolean expectClosingParenthesis, boolean isFileName, boolean checkJavaIdentifiers, boolean allowGenerics, boolean replaceSystemProperties, boolean replaceExternalClassNames, boolean replaceExternalTypes, List list) throws ParseException, IOException { if (list == null) { list = new ArrayList(); } // Parse a new list and add it to our list. list.add(parseCommaSeparatedList(expectedDescription, readFirstWord, allowEmptyList, expectClosingParenthesis, isFileName, checkJavaIdentifiers, allowGenerics, replaceSystemProperties, replaceExternalClassNames, replaceExternalTypes, null)); return list; } /** * Reads a comma-separated list of java identifiers or of file names. * Examples of invocation arguments: * * expected read allow expect is check allow replace replace replace * description First empty Closing File Java Generic System Extern Extern * Word List Paren Name Id Prop Class Types * ---------------------------------------------------------------------------------- * ("directory name", true, true, false, true, false, true, true, false, false, ...) * ("optimization", true, false, false, false, false, true, false, false, false, ...) * ("package name", true, true, false, false, true, false, false, true, false, ...) * ("attribute name", true, true, false, false, true, false, false, false, false, ...) * ("class name", true, true, false, false, true, false, false, true, false, ...) * ("filter", true, true, true, true, false, true, true, false, false, ...) * ("annotation ", false, false, false, false, true, false, false, false, true, ...) * ("class name ", true, false, false, false, true, false, false, false, false, ...) * ("annotation ", true, false, false, false, true, false, false, false, true, ...) * ("class name ", false, false, false, false, true, false, false, false, false, ...) * ("annotation ", true, false, false, false, true, false, false, false, true, ...) * ("argument", true, true, true, false, true, false, false, false, false, ...) */ protected List parseCommaSeparatedList(String expectedDescription, boolean readFirstWord, boolean allowEmptyList, boolean expectClosingParenthesis, boolean isFileName, boolean checkJavaIdentifiers, boolean allowGenerics, boolean replaceSystemProperties, boolean replaceExternalClassNames, boolean replaceExternalTypes, List list) throws ParseException, IOException { return parseCommaSeparatedList(expectedDescription, readFirstWord, allowEmptyList, null, expectClosingParenthesis, isFileName, checkJavaIdentifiers, allowGenerics, replaceSystemProperties, replaceExternalClassNames, replaceExternalTypes, list); } protected List parseCommaSeparatedList(String expectedDescription, boolean readFirstWord, boolean allowEmptyList, String defaultIfEmpty, boolean expectClosingParenthesis, boolean isFileName, boolean checkJavaIdentifiers, boolean allowGenerics, boolean replaceSystemProperties, boolean replaceExternalClassNames, boolean replaceExternalTypes, List list) throws ParseException, IOException { if (list == null) { list = new ArrayList(); } if (readFirstWord) { if (!allowEmptyList) { // Read the first list entry. readNextWord(expectedDescription, isFileName, true, false); } else if (expectClosingParenthesis) { // Read the first list entry. readNextWord(expectedDescription, isFileName, true, false); // Return if the entry is actually empty (an empty file name or // a closing parenthesis). if (nextWord.length() == 0) { // Read the closing parenthesis readNextWord("closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + "'"); if (defaultIfEmpty != null) { list.add(defaultIfEmpty); } return list; } else if (nextWord.equals(ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD)) { if (defaultIfEmpty != null) { list.add(defaultIfEmpty); } return list; } } else { // Read the first list entry, if there is any. readNextWord(isFileName, true); // Check if the list is empty. if (configurationEnd()) { if (defaultIfEmpty != null) { list.add(defaultIfEmpty); } return list; } } } while (true) { if (checkJavaIdentifiers) { checkJavaIdentifier("java type", allowGenerics); } if (replaceSystemProperties) { nextWord = replaceSystemProperties(nextWord); } if (replaceExternalClassNames) { nextWord = ClassUtil.internalClassName(nextWord); } if (replaceExternalTypes) { nextWord = ClassUtil.internalType(nextWord); } list.add(nextWord); if (expectClosingParenthesis) { // Read a comma (or a closing parenthesis, or a different word). readNextWord("separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + "' or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + "'"); } else { // Read a comma (or a different word). readNextWord(); } if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord)) { return list; } // Read the next list entry. readNextWord(expectedDescription, isFileName, true, false); } } /** * Converts a list of class name matchers to a class specification. */ protected ClassSpecification convertToClassSpecification(List list) { return new ClassSpecification(null, 0, 0, null, StringUtil.join(",", list.toArray(new String[list.size()])), null, null); } /** * Removes any present member specification from the provided * class specification. */ protected ClassSpecification removeMemberSpecification(ClassSpecification classSpecification) { classSpecification.fieldSpecifications = null; classSpecification.methodSpecifications = null; return classSpecification; } /** * Throws a ParseException for an unexpected keyword. */ protected int unknownAccessFlag() throws ParseException { throw new ParseException("Unexpected keyword " + reader.locationDescription()); } /** * Creates a properly resolved URL, based on the given word. */ protected URL url(String word) throws ParseException, MalformedURLException { String fileName = replaceSystemProperties(word); URL url; try { // Check if the file name is a valid URL. url = new URL(fileName); return url; } catch (MalformedURLException ex) {} // Is it relative to a URL or to a file? URL baseURL = reader.getBaseURL(); if (baseURL != null) { url = new URL(baseURL, fileName); } else { // Is the file a valid resource URL? url = ConfigurationParser.class.getResource(fileName); if (url == null) { File file = new File(fileName); // Try to get an absolute file. if (!file.isAbsolute()) { file = new File(reader.getBaseDir(), fileName); } url = file.toURI().toURL(); } } return url; } /** * Creates a properly resolved File, based on the given word. */ protected File file(String word) throws ParseException { String fileName = replaceSystemProperties(word); File file = new File(fileName); // Try to get an absolute file. if (!file.isAbsolute()) { file = new File(reader.getBaseDir(), fileName); } return file; } /** * Replaces any properties in the given word by their values. * For instance, the substring "" is replaced by its value. */ protected String replaceSystemProperties(String word) throws ParseException { int fromIndex = 0; while (true) { fromIndex = word.indexOf(ConfigurationConstants.OPEN_SYSTEM_PROPERTY, fromIndex); if (fromIndex < 0) { break; } int toIndex = word.indexOf(ConfigurationConstants.CLOSE_SYSTEM_PROPERTY, fromIndex+1); if (toIndex < 0) { break; } String propertyName = word.substring(fromIndex+1, toIndex); String propertyValue = properties.getProperty(propertyName); if (propertyValue == null) { try { // Allow integer names, since they may be references // to wildcards. Integer.parseInt(propertyName); fromIndex = toIndex + 1; continue; } catch (NumberFormatException e) { throw new ParseException("Value of system property '" + propertyName + "' is undefined in " + reader.locationDescription()); } } word = word.substring(0, fromIndex) + propertyValue + word.substring(toIndex+1); fromIndex += propertyValue.length(); } return word; } /** * Reads the next word of the configuration in the 'nextWord' field, * throwing an exception if there is no next word. */ protected void readNextWord(String expectedDescription) throws ParseException, IOException { readNextWord(expectedDescription, false, false, false); } /** * Reads the next word of the configuration in the 'nextWord' field, * throwing an exception if there is no next word. */ protected void readNextWord(String expectedDescription, boolean isFileName, boolean expectSingleFile, boolean expectingAtCharacter) throws ParseException, IOException { readNextWord(isFileName, expectSingleFile); if (configurationEnd(expectingAtCharacter)) { throw new ParseException("Expecting " + expectedDescription + " before " + reader.locationDescription()); } } /** * Reads the next word of the configuration in the 'nextWord' field. */ protected void readNextWord() throws IOException { readNextWord(false, false); } /** * Reads the next word of the configuration in the 'nextWord' field. */ protected void readNextWord(boolean isFileName, boolean expectSingleFile) throws IOException { nextWord = reader.nextWord(isFileName, expectSingleFile); } /** * Returns whether the end of the configuration has been reached. */ protected boolean configurationEnd() { return configurationEnd(false); } /** * Returns whether the end of the configuration has been reached. */ protected boolean configurationEnd(boolean expectingAtCharacter) { return nextWord == null || nextWord.startsWith(ConfigurationConstants.OPTION_PREFIX) || (!expectingAtCharacter && nextWord.equals(ConfigurationConstants.AT_DIRECTIVE)); } /** * Checks whether the given word is a valid Java identifier and throws * a ParseException if it isn't. Wildcard characters are accepted. */ protected void checkJavaIdentifier(String expectedDescription) throws ParseException { checkJavaIdentifier(expectedDescription, true); } /** * Checks whether the given word is a valid Java identifier and throws * a ParseException if it isn't. Wildcard characters are accepted. */ protected void checkJavaIdentifier(String expectedDescription, boolean allowGenerics) throws ParseException { if (!isJavaIdentifier(nextWord)) { throw new ParseException("Expecting " + expectedDescription + " before " + reader.locationDescription()); } if (!allowGenerics && containsGenerics(nextWord)) { throw new ParseException("Generics are not allowed (erased) in " + expectedDescription + " " + reader.locationDescription()); } } /** * Returns whether the given word is a valid Java identifier. * Wildcard characters are accepted. */ protected boolean isJavaIdentifier(String word) { if (word.length() == 0) { return false; } for (int index = 0; index < word.length(); index++) { char c = word.charAt(index); if (!(Character.isJavaIdentifierPart(c) || c == '.' || c == '[' || c == ']' || c == '<' || c == '>' || c == '-' || c == '!' || c == '*' || c == '?' || c == '%')) { return false; } } return true; } /** * Returns whether the given word contains angle brackets around * a non-digit string. */ protected boolean containsGenerics(String word) { int index = 0; while (true) { // Can we find an opening angular bracket? int openIndex = word.indexOf(TypeConstants.GENERIC_START, index); if (openIndex < 0) { return false; } // Can we find a corresponding closing angular bracket? int closeIndex = word.indexOf(TypeConstants.GENERIC_END, openIndex + 1); if (closeIndex < 0) { return false; } try { // Is it just a reference to a wildcard? Integer.parseInt(word.substring(openIndex + 1, closeIndex)); } catch (NumberFormatException e) { // It's not; it's really a generic type. return true; } index = closeIndex + 1; } } /** * Checks whether the given access flags are valid field access flags, * throwing a ParseException if they aren't. */ protected void checkFieldAccessFlags(int requiredSetMemberAccessFlags, int requiredUnsetMemberAccessFlags) throws ParseException { if (((requiredSetMemberAccessFlags | requiredUnsetMemberAccessFlags) & ~AccessConstants.VALID_FLAGS_FIELD) != 0) { throw new ParseException("Invalid method access modifier for field before " + reader.locationDescription()); } } /** * Checks whether the given access flags are valid method access flags, * throwing a ParseException if they aren't. */ protected void checkMethodAccessFlags(int requiredSetMemberAccessFlags, int requiredUnsetMemberAccessFlags) throws ParseException { if (((requiredSetMemberAccessFlags | requiredUnsetMemberAccessFlags) & ~AccessConstants.VALID_FLAGS_METHOD) != 0) { throw new ParseException("Invalid field access modifier for method before " + reader.locationDescription()); } } /** * A main method for testing configuration parsing. */ public static void main(String[] args) { try { try (ConfigurationParser parser = new ConfigurationParser(args, System.getProperties())) { parser.parse(new Configuration()); } catch (ParseException ex) { ex.printStackTrace(); } } catch (IOException ex) { ex.printStackTrace(); } } } ================================================ FILE: base/src/main/java/proguard/ConfigurationVerifier.java ================================================ /* * 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.util.WarningLogger; import proguard.classfile.util.WarningPrinter; import java.io.*; /** * This class performs sanity checks on a given configurations. * * @author Eric Lafortune */ public class ConfigurationVerifier { private static final Logger logger = LogManager.getLogger(ConfigurationVerifier.class); private final Configuration configuration; /** * Creates a new ConfigurationVerifier with the given configuration. */ public ConfigurationVerifier(Configuration configuration) { this.configuration = configuration; } /** * Checks the given configuration for potential problems. */ public void check() throws IOException { ClassPath programJars = configuration.programJars; ClassPath libraryJars = configuration.libraryJars; // Check that the input isn't empty. if (programJars == null) { throw new IOException("The input is empty. You have to specify one or more '-injars' options."); } checkInputJarFirst(programJars); checkOutputJarFilter(programJars); // Check for conflicts between input/output entries of the class paths. checkConflicts(programJars, programJars); checkConflicts(programJars, libraryJars); checkConflicts(libraryJars, libraryJars); printNotes(configuration, programJars, logger); } /** * Checks that the input application is specified before any input libraries. */ private void checkInputJarFirst(ClassPath programJars) throws IOException { // Check that the first jar is an input jar. ClassPathEntry firstEntry = programJars.get(0); if (firstEntry.isOutput()) { throw new IOException("The output jar [" + firstEntry.getName() + "] must be specified after an input jar, or it will be empty."); } } /** * Checks that the first of two subsequent output jars has a filter. */ private void checkOutputJarFilter(ClassPath programJars) throws IOException { // Check that the first of two subsequent the output jars has a filter. for (int index = 0; index < programJars.size() - 1; index++) { ClassPathEntry entry = programJars.get(index); if (entry.isOutput() && !entry.isFiltered() && programJars.get(index + 1).isOutput()) { throw new IOException("The output jar [" + entry.getName() + "] must have a filter, or all subsequent output jars will be empty."); } } } /** * Performs some sanity checks on the class paths. */ private void checkConflicts(ClassPath classPath1, ClassPath classPath2) throws IOException { if (classPath1 == null || classPath2 == null) { return; } for (int index1 = 0; index1 < classPath1.size(); index1++) { ClassPathEntry entry1 = classPath1.get(index1); for (int index2 = 0; index2 < classPath2.size(); index2++) { if (classPath1 != classPath2 || index1 != index2) { ClassPathEntry entry2 = classPath2.get(index2); if (entry2.getName().equals(entry1.getName())) { if (entry1.isOutput()) { if (entry2.isOutput()) { // Output / output. throw new IOException("The same output jar ["+entry1.getName()+"] is specified twice."); } else { // Output / input. throw new IOException("Input jars and output jars must be different ["+entry1.getName()+"]."); } } else { if (entry2.isOutput()) { // Input / output. throw new IOException("Input jars and output jars must be different ["+entry1.getName()+"]."); } else if (!entry1.isFiltered() || !entry2.isFiltered()) { // Input / input. throw new IOException("The same input jar ["+entry1.getName()+"] is specified twice."); } } } } } } } private void printNotes(Configuration configuration, ClassPath programJars, Logger logger) throws IOException { // Print out some general notes if necessary. if ((configuration.note == null || !configuration.note.isEmpty())) { // Check for potential problems with mixed-case class names on // case-insensitive file systems. if (configuration.obfuscate && configuration.useMixedCaseClassNames && configuration.classObfuscationDictionary == null) { String os = System.getProperty("os.name").toLowerCase(); if (os.startsWith("windows") || os.startsWith("mac os")) { // Go over all program class path entries. for (int index = 0; index < programJars.size(); index++) { // Is it an output directory? ClassPathEntry entry = programJars.get(index); if (entry.isOutput() && !entry.isApk() && !entry.isJar() && !entry.isAar() && !entry.isWar() && !entry.isEar() && !entry.isJmod() && !entry.isZip()) { logger.info("Note: you're writing the processed class files to a directory [{}].", entry.getName()); logger.info(" This will likely cause problems with obfuscated mixed-case class names."); logger.info(" You should consider writing the output to a jar file, or otherwise"); logger.info(" specify '-dontusemixedcaseclassnames'."); break; } } } } // Check if -adaptresourcefilecontents has a proper filter. if (configuration.adaptResourceFileContents != null && (configuration.adaptResourceFileContents.isEmpty() || configuration.adaptResourceFileContents.get(0).equals(ConfigurationConstants.ANY_FILE_KEYWORD))) { logger.info("Note: you're specifying '-adaptresourcefilecontents' for all resource files."); logger.info(" This will most likely cause problems with binary files."); } // Check if all -keepclassmembers options indeed have class members. WarningPrinter keepClassMemberNotePrinter = new WarningLogger(logger, configuration.note); new KeepClassMemberChecker(keepClassMemberNotePrinter).checkClassSpecifications(configuration.keep); // Check if -assumenosideffects options don't specify all methods. WarningPrinter assumeNoSideEffectsNotePrinter = new WarningLogger(logger, configuration.note); new AssumeNoSideEffectsChecker(assumeNoSideEffectsNotePrinter).checkClassSpecifications(configuration.assumeNoSideEffects); // Print out a summary of the notes, if necessary. int keepClassMemberNoteCount = keepClassMemberNotePrinter.getWarningCount(); if (keepClassMemberNoteCount > 0) { logger.info("Note: there were {} '-keepclassmembers' options that didn't specify class", keepClassMemberNoteCount); logger.info(" members. You should specify at least some class members or consider"); logger.info(" if you just need '-keep'."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#classmembers)"); } int assumeNoSideEffectsNoteCount = assumeNoSideEffectsNotePrinter.getWarningCount(); if (assumeNoSideEffectsNoteCount > 0) { logger.info("Note: there were {} '-assumenosideeffects' options that try to match all", assumeNoSideEffectsNoteCount); logger.info(" methods with wildcards. This will likely cause problems with methods like"); logger.info(" 'wait()' and 'notify()'. You should specify the methods more precisely."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#nosideeffects)"); } } } } ================================================ FILE: base/src/main/java/proguard/ConfigurationWriter.java ================================================ /* * 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import proguard.optimize.Optimizer; import proguard.util.*; import java.io.*; import java.net.*; import java.util.*; /** * This class writes ProGuard configurations to a file. * * @author Eric Lafortune */ public class ConfigurationWriter implements AutoCloseable { private static final Logger logger = LogManager.getLogger(ConfigurationWriter.class); private static final String[] KEEP_OPTIONS = new String[] { ConfigurationConstants.KEEP_OPTION, ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION, ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION, ConfigurationConstants.KEEP_CODE_OPTION }; private final PrintWriter writer; private File baseDir; /** * Creates a new ConfigurationWriter for the given file name. */ public ConfigurationWriter(File configurationFile) throws IOException { this(PrintWriterUtil.createPrintWriterOut(configurationFile)); baseDir = configurationFile.getParentFile(); } /** * Creates a new ConfigurationWriter for the given PrintWriter. */ public ConfigurationWriter(PrintWriter writer) throws IOException { this.writer = writer; } /** * Closes this ConfigurationWriter. */ @Override public void close() throws IOException { PrintWriterUtil.closePrintWriter(baseDir, writer); } /** * Writes the given configuration. * @param configuration the configuration that is to be written out. * @throws IOException if an IO error occurs while writing the configuration. */ public void write(Configuration configuration) throws IOException { if (configuration.printConfiguration != null) { logger.info("Printing configuration to [{}]...", PrintWriterUtil.fileName(configuration.printConfiguration)); } // Write the program class path (input and output entries). writeJarOptions(ConfigurationConstants.INJARS_OPTION, ConfigurationConstants.OUTJARS_OPTION, configuration.programJars); writer.println(); // Write the library class path (output entries only). writeJarOptions(ConfigurationConstants.LIBRARYJARS_OPTION, ConfigurationConstants.LIBRARYJARS_OPTION, configuration.libraryJars); writer.println(); // Write the other options. writeOption(ConfigurationConstants.SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION, configuration.skipNonPublicLibraryClasses); writeOption(ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION, !configuration.skipNonPublicLibraryClassMembers); writeOption(ConfigurationConstants.KEEP_DIRECTORIES_OPTION, configuration.keepDirectories); writeOption(ConfigurationConstants.DONT_COMPRESS_OPTION, configuration.dontCompress); writeOption(ConfigurationConstants.ZIP_ALIGN_OPTION, configuration.zipAlign); writeOption(ConfigurationConstants.TARGET_OPTION, ClassUtil.externalClassVersion(configuration.targetClassVersion)); writeOption(ConfigurationConstants.FORCE_PROCESSING_OPTION, configuration.lastModified == Long.MAX_VALUE); writeOption(ConfigurationConstants.DONT_SHRINK_OPTION, !configuration.shrink); writeOption(ConfigurationConstants.PRINT_USAGE_OPTION, configuration.printUsage); writeOption(ConfigurationConstants.DONT_OPTIMIZE_OPTION, !configuration.optimize); writeOption(ConfigurationConstants.OPTIMIZATIONS, configuration.optimizations); writeOption(ConfigurationConstants.OPTIMIZATION_PASSES, configuration.optimizationPasses); writeOption(ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION, configuration.allowAccessModification); writeOption(ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION, configuration.mergeInterfacesAggressively); writeOption(ConfigurationConstants.DONT_OBFUSCATE_OPTION, !configuration.obfuscate); writeOption(ConfigurationConstants.PRINT_MAPPING_OPTION, configuration.printMapping); writeOption(ConfigurationConstants.APPLY_MAPPING_OPTION, configuration.applyMapping); writeOption(ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION, configuration.obfuscationDictionary); writeOption(ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION, configuration.classObfuscationDictionary); writeOption(ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION, configuration.packageObfuscationDictionary); writeOption(ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION, configuration.overloadAggressively); writeOption(ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION, configuration.useUniqueClassMemberNames); writeOption(ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION, !configuration.useMixedCaseClassNames); writeOption(ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION, configuration.keepPackageNames, true); writeOption(ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION, configuration.flattenPackageHierarchy, true); writeOption(ConfigurationConstants.REPACKAGE_CLASSES_OPTION, configuration.repackageClasses, true); writeOption(ConfigurationConstants.KEEP_ATTRIBUTES_OPTION, configuration.keepAttributes); writeOption(ConfigurationConstants.KEEP_PARAMETER_NAMES_OPTION, configuration.keepParameterNames); writeOption(ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION, configuration.newSourceFileAttribute); writeOption(ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION, configuration.adaptClassStrings, true); writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION, configuration.adaptResourceFileNames); writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION, configuration.adaptResourceFileContents); writeOption(ConfigurationConstants.KEEP_KOTLIN_METADATA, configuration.keepKotlinMetadata); writeOption(ConfigurationConstants.DONT_PROCESS_KOTLIN_METADATA, configuration.dontProcessKotlinMetadata); writeOption(ConfigurationConstants.DONT_PREVERIFY_OPTION, !configuration.preverify); writeOption(ConfigurationConstants.MICRO_EDITION_OPTION, configuration.microEdition); writeOption(ConfigurationConstants.ANDROID_OPTION, configuration.android); writeOptions(ConfigurationConstants.KEY_STORE_OPTION, configuration.keyStores, ConfigurationConstants.KEY_STORE_PASSWORD_OPTION, configuration.keyStorePasswords, ConfigurationConstants.KEY_ALIAS_OPTION, configuration.keyAliases, ConfigurationConstants.KEY_PASSWORD_OPTION, configuration.keyPasswords); writeOption(ConfigurationConstants.VERBOSE_OPTION, configuration.verbose); writeOption(ConfigurationConstants.DONT_NOTE_OPTION, configuration.note, true); writeOption(ConfigurationConstants.DONT_WARN_OPTION, configuration.warn, true); writeOption(ConfigurationConstants.IGNORE_WARNINGS_OPTION, configuration.ignoreWarnings); writeOption(ConfigurationConstants.PRINT_CONFIGURATION_OPTION, configuration.printConfiguration); writeOption(ConfigurationConstants.DUMP_OPTION, configuration.dump); writeOption(ConfigurationConstants.ADD_CONFIGURATION_DEBUGGING_OPTION, configuration.addConfigurationDebugging); writeOption(ConfigurationConstants.PRINT_SEEDS_OPTION, configuration.printSeeds); writeOption(ConfigurationConstants.OPTIMIZE_AGGRESSIVELY, !configuration.optimizeConservatively); writer.println(); // Write the "why are you keeping" options. writeOptions(ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION, configuration.whyAreYouKeeping); writer.println(); // Write the keep options. writeOptions(KEEP_OPTIONS, configuration.keep); // Write the "no side effect methods" options. writeOptions(ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION, configuration.assumeNoSideEffects); writeOptions(ConfigurationConstants.ASSUME_NO_EXTERNAL_SIDE_EFFECTS_OPTION, configuration.assumeNoExternalSideEffects); writeOptions(ConfigurationConstants.ASSUME_NO_ESCAPING_PARAMETERS_OPTION, configuration.assumeNoEscapingParameters); writeOptions(ConfigurationConstants.ASSUME_NO_EXTERNAL_RETURN_VALUES_OPTION, configuration.assumeNoExternalReturnValues); writeOptions(ConfigurationConstants.ASSUME_VALUES_OPTION, configuration.assumeValues); if (writer.checkError()) { throw new IOException("Can't write configuration"); } } private void writeJarOptions(String inputEntryOptionName, String outputEntryOptionName, ClassPath classPath) { if (classPath != null) { for (int index = 0; index < classPath.size(); index++) { ClassPathEntry entry = classPath.get(index); String optionName = entry.isOutput() ? outputEntryOptionName : inputEntryOptionName; writer.print(optionName); // Append the feature name, if any, as a suboption. String featureName = entry.getFeatureName(); if (featureName != null) { writer.print(ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD); writer.print(featureName); } writer.print(' '); writer.print(relativeFileName(entry.getFile())); // Append the filters, if any. boolean filtered = false; // For backward compatibility, the jmod/aar/aab/apk filters // come first. filtered = writeFilter(filtered, entry.getJmodFilter()); filtered = writeFilter(filtered, entry.getAarFilter()); filtered = writeFilter(filtered, entry.getAabFilter()); filtered = writeFilter(filtered, entry.getApkFilter()); filtered = writeFilter(filtered, entry.getZipFilter()); filtered = writeFilter(filtered, entry.getJmodFilter()); filtered = writeFilter(filtered, entry.getEarFilter()); filtered = writeFilter(filtered, entry.getWarFilter()); filtered = writeFilter(filtered, entry.getJarFilter()); filtered = writeFilter(filtered, entry.getFilter()); if (filtered) { writer.print(ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD); } writer.println(); } } } private boolean writeFilter(boolean filtered, List filter) { if (filtered) { writer.print(ConfigurationConstants.SEPARATOR_KEYWORD); } if (filter != null) { if (!filtered) { writer.print(ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD); } writer.print(ListUtil.commaSeparatedString(filter, true)); filtered = true; } return filtered; } private void writeOption(String optionName, boolean flag) { if (flag) { writer.println(optionName); } } private void writeOption(String optionName, int argument) { if (argument != 1) { writer.print(optionName); writer.print(' '); writer.println(argument); } } private void writeOption(String optionName, List arguments) { writeOption(optionName, arguments, false); } private void writeOptions(String optionName, List arguments, boolean replaceInternalClassNames) { if (arguments != null) { for (int index = 0; index < arguments.size(); index++) { writeOption(optionName, (List)arguments.get(index), replaceInternalClassNames); } } } private void writeOptions(String optionName1, List arguments1, String optionName2, List arguments2, String optionName3, List arguments3, String optionName4, List arguments4) { for (int index = 0; true; index++) { boolean written = false; if (arguments1 != null && index < arguments1.size()) { writeOption(optionName1, arguments1.get(index)); written = true; } if (arguments2 != null && index < arguments2.size()) { writeOption(optionName2, arguments2.get(index)); written = true; } if (arguments3 != null && index < arguments3.size()) { writeOption(optionName3, arguments3.get(index)); written = true; } if (arguments4 != null && index < arguments4.size()) { writeOption(optionName4, arguments4.get(index)); written = true; } if (!written) { break; } } } private void writeOption(String optionName, List arguments, boolean replaceInternalClassNames) { if (arguments != null) { if (arguments.isEmpty()) { writer.println(optionName); } else { if (replaceInternalClassNames) { arguments = externalClassNames(arguments); } writer.print(optionName); writer.print(' '); writer.println(ListUtil.commaSeparatedString(arguments, true)); } } } private void writeOption(String optionName, String arguments) { writeOption(optionName, arguments, false); } private void writeOption(String optionName, String arguments, boolean replaceInternalClassNames) { if (arguments != null) { if (replaceInternalClassNames) { arguments = ClassUtil.externalClassName(arguments); } writer.print(optionName); writer.print(' '); writer.println(quotedString(arguments)); } } private void writeOption(String optionName, URL url) { if (url != null) { if (url.getPath().length() > 0) { String fileName = url.toExternalForm(); if (url.getProtocol().equals("file")) { try { fileName = relativeFileName(new File(url.toURI())); } catch (URISyntaxException ignore) {} } else { } writer.print(optionName); writer.print(' '); writer.println(fileName); } else { writer.println(optionName); } } } private void writeOption(String optionName, File file) { if (file != null) { if (file.getPath().length() > 0) { writer.print(optionName); writer.print(' '); writer.println(relativeFileName(file)); } else { writer.println(optionName); } } } private void writeOptions(String[] optionNames, List keepClassSpecifications) { if (keepClassSpecifications != null) { for (int index = 0; index < keepClassSpecifications.size(); index++) { writeOption(optionNames, (KeepClassSpecification)keepClassSpecifications.get(index)); } } } private void writeOption(String[] optionNames, KeepClassSpecification keepClassSpecification) { if (keepClassSpecification.condition != null) { writeOption(ConfigurationConstants.IF_OPTION, keepClassSpecification.condition); } // Compose the option name. String optionName = optionNames[keepClassSpecification.markConditionally ? 2 : keepClassSpecification.markClasses ? 0 : keepClassSpecification.markClassMembers ? 1 : 3]; if (keepClassSpecification.markDescriptorClasses) { optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ConfigurationConstants.INCLUDE_DESCRIPTOR_CLASSES_SUBOPTION; } if ((keepClassSpecification.markClasses || keepClassSpecification.markClassMembers) && keepClassSpecification.markCodeAttributes) { optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ConfigurationConstants.INCLUDE_CODE_SUBOPTION; } if (keepClassSpecification.allowShrinking) { optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION; } if (keepClassSpecification.allowOptimization) { optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION; } if (keepClassSpecification.allowObfuscation) { optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION; } // Write out the option with the proper class specification. writeOption(optionName, keepClassSpecification); } private void writeOptions(String optionName, List classSpecifications) { if (classSpecifications != null) { for (int index = 0; index < classSpecifications.size(); index++) { writeOption(optionName, (ClassSpecification)classSpecifications.get(index)); } } } private void writeOption(String optionName, ClassSpecification classSpecification) { writer.println(); // Write out the comments for this option. writeComments(classSpecification.comments); writer.print(optionName); writer.print(' '); // Write out the required annotation, if any. if (classSpecification.annotationType != null) { writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); writer.print(ClassUtil.externalType(classSpecification.annotationType)); writer.print(' '); } // Write out the class access flags. writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredUnsetAccessFlags, ConfigurationConstants.NEGATOR_KEYWORD)); writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredSetAccessFlags)); // Write out the class keyword, if we didn't write the interface // keyword earlier. if (((classSpecification.requiredSetAccessFlags | classSpecification.requiredUnsetAccessFlags) & (AccessConstants.INTERFACE | AccessConstants.ENUM | AccessConstants.MODULE)) == 0) { writer.print(ConfigurationConstants.CLASS_KEYWORD); } writer.print(' '); // Write out the class name. writer.print(classSpecification.className != null ? ClassUtil.externalClassName(classSpecification.className) : ConfigurationConstants.ANY_CLASS_KEYWORD); // Write out the extends template, if any. if (classSpecification.extendsAnnotationType != null || classSpecification.extendsClassName != null) { writer.print(' '); writer.print(ConfigurationConstants.EXTENDS_KEYWORD); writer.print(' '); // Write out the required extends annotation, if any. if (classSpecification.extendsAnnotationType != null) { writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); writer.print(ClassUtil.externalType(classSpecification.extendsAnnotationType)); writer.print(' '); } // Write out the extended class name. writer.print(classSpecification.extendsClassName != null ? ClassUtil.externalClassName(classSpecification.extendsClassName) : ConfigurationConstants.ANY_CLASS_KEYWORD); } // Write out the keep field and keep method options, if any. if (classSpecification.fieldSpecifications != null || classSpecification.methodSpecifications != null || classSpecification.memberComments != null) { writer.print(' '); writer.println(ConfigurationConstants.OPEN_KEYWORD); writeFieldSpecification( classSpecification.fieldSpecifications); writeMethodSpecification(classSpecification.methodSpecifications); writeComments(classSpecification.memberComments); writer.println(ConfigurationConstants.CLOSE_KEYWORD); } else { writer.println(); } } private void writeComments(String comments) { if (comments != null) { int index = 0; while (index < comments.length()) { int breakIndex = comments.indexOf('\n', index); if (breakIndex < 0) { breakIndex = comments.length(); } writer.print('#'); if (comments.charAt(index) != ' ') { writer.print(' '); } writer.println(comments.substring(index, breakIndex)); index = breakIndex + 1; } } } private void writeFieldSpecification(List memberSpecifications) { if (memberSpecifications != null) { for (int index = 0; index < memberSpecifications.size(); index++) { MemberSpecification memberSpecification = (MemberSpecification)memberSpecifications.get(index); writer.print(" "); // Write out the required annotation, if any. if (memberSpecification.annotationType != null) { writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); writer.println(ClassUtil.externalType(memberSpecification.annotationType)); writer.print(" "); } // Write out the field access flags. writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredUnsetAccessFlags, ConfigurationConstants.NEGATOR_KEYWORD)); writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredSetAccessFlags)); // Write out the field name and descriptor. String name = memberSpecification.name; String descriptor = memberSpecification.descriptor; writer.print(descriptor == null ? name == null ? ConfigurationConstants.ANY_FIELD_KEYWORD : ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name : ClassUtil.externalFullFieldDescription(0, name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name, descriptor)); writeValueAssignment(ConfigurationConstants.EQUAL_KEYWORD, memberSpecification); writer.println(ConfigurationConstants.SEPARATOR_KEYWORD); } } } private void writeMethodSpecification(List memberSpecifications) { if (memberSpecifications != null) { for (int index = 0; index < memberSpecifications.size(); index++) { MemberSpecification memberSpecification = (MemberSpecification)memberSpecifications.get(index); writer.print(" "); // Write out the required annotation, if any. if (memberSpecification.annotationType != null) { writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); writer.println(ClassUtil.externalType(memberSpecification.annotationType)); writer.print(" "); } // Write out the method access flags. writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredUnsetAccessFlags, ConfigurationConstants.NEGATOR_KEYWORD)); writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredSetAccessFlags)); // Write out the method name and descriptor. String name = memberSpecification.name; String descriptor = memberSpecification.descriptor; writer.print(descriptor == null ? name == null ? ConfigurationConstants.ANY_METHOD_KEYWORD : ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + ConfigurationConstants.ANY_ARGUMENTS_KEYWORD + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD : ClassUtil.externalFullMethodDescription(ClassConstants.METHOD_NAME_INIT, 0, name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name, descriptor)); writeValueAssignment(ConfigurationConstants.RETURN_KEYWORD, memberSpecification); writer.println(ConfigurationConstants.SEPARATOR_KEYWORD); } } } private void writeValueAssignment(String assignmentKeyword, MemberSpecification memberSpecification) { if (memberSpecification instanceof MemberValueSpecification) { MemberValueSpecification memberValueSpecification = (MemberValueSpecification)memberSpecification; Number[] values = memberValueSpecification.values; if (values != null) { writer.print(' '); writer.print(assignmentKeyword); writer.print(' '); // Write the first value. // Is it a boolean? String descriptor = memberSpecification.descriptor; if (descriptor != null && ClassUtil.internalMethodReturnType(descriptor).equals("" + TypeConstants.BOOLEAN)) { // It's a boolean (represented as an integer). writer.print(values[0].intValue() != 0); } else { // It's a number. writer.print(values[0]); } // Write the second value of the range, if any. if (values.length > 1) { writer.print(ConfigurationConstants.RANGE_KEYWORD); writer.print(values[1]); } } } } /** * Returns a list with external versions of the given list of internal * class names. */ private List externalClassNames(List internalClassNames) { List externalClassNames = new ArrayList(internalClassNames.size()); for (int index = 0; index < internalClassNames.size(); index++) { externalClassNames.add(ClassUtil.externalClassName((String)internalClassNames.get(index))); } return externalClassNames; } /** * Returns a relative file name of the given file, if possible. * The file name is also quoted, if necessary. */ private String relativeFileName(File file) { String fileName = file.getAbsolutePath(); // See if we can convert the file name into a relative file name. if (baseDir != null) { String baseDirName = baseDir.getAbsolutePath() + File.separator; if (fileName.startsWith(baseDirName)) { fileName = fileName.substring(baseDirName.length()); } } return quotedString(fileName); } /** * Returns a quoted version of the given string, if necessary. */ private String quotedString(String string) { return string.length() == 0 || string.indexOf(' ') >= 0 || string.indexOf('@') >= 0 || string.indexOf('{') >= 0 || string.indexOf('}') >= 0 || string.indexOf('(') >= 0 || string.indexOf(')') >= 0 || string.indexOf(':') >= 0 || string.indexOf(';') >= 0 || string.indexOf(',') >= 0 ? ("'" + string + "'") : ( string ); } /** * A main method for testing configuration writing. */ public static void main(String[] args) { try (ConfigurationWriter writer = new ConfigurationWriter(new File(args[0]))) { writer.write(new Configuration()); } catch (Exception ex) { ex.printStackTrace(); } } } ================================================ FILE: base/src/main/java/proguard/DataEntryReaderFactory.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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; import proguard.io.DataEntryNameFilter; import proguard.io.DataEntryReader; import proguard.io.FilteredDataEntryReader; import proguard.io.JarReader; import proguard.io.PrefixStrippingDataEntryReader; import proguard.io.RenamedDataEntryReader; import proguard.util.AndMatcher; import proguard.util.ExtensionMatcher; import proguard.util.FileNameParser; import proguard.util.ListFunctionParser; import proguard.util.ListParser; import proguard.util.NotMatcher; import proguard.util.SingleFunctionParser; import proguard.util.StringFunction; import proguard.util.StringMatcher; import proguard.util.WildcardManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static proguard.classfile.ClassConstants.CLASS_FILE_EXTENSION; /** * This class can create DataEntryReader instances based on class path entries. * The readers will unwrap the input data entries from any jars, wars, ears, * jmods, and zips before passing them to a given reader. * * @author Eric Lafortune */ public class DataEntryReaderFactory { private static final String VERSIONS_PATTERN = "META-INF/versions"; private static final String VERSIONS_EXCLUDE = "!META-INF/versions/**"; private static final String JMOD_CLASS_FILE_PREFIX = "classes/"; private final boolean android; /** * Creates a new DataEntryReaderFactory. * * @param android Specifies whether the packaging is targeted at the * Android platform. Archives inside the assets directory * then aren't unpacked but simply read as data files. */ public DataEntryReaderFactory(boolean android) { this.android = android; } /** * Creates a DataEntryReader that can read the given class path entry. * * @param classPathEntry the input class path entry. * @param reader a data entry reader to which the reading of actual * classes and resource files can be delegated. * @return a DataEntryReader for reading the given class path entry. */ public DataEntryReader createDataEntryReader(ClassPathEntry classPathEntry, DataEntryReader reader) { boolean isApk = classPathEntry.isApk(); boolean isAab = classPathEntry.isAab(); boolean isJar = classPathEntry.isJar(); boolean isAar = classPathEntry.isAar(); boolean isWar = classPathEntry.isWar(); boolean isEar = classPathEntry.isEar(); boolean isJmod = classPathEntry.isJmod(); boolean isZip = classPathEntry.isZip(); List filter = getFilterExcludingVersionedClasses(classPathEntry); List apkFilter = classPathEntry.getApkFilter(); List aabFilter = classPathEntry.getAabFilter(); List jarFilter = classPathEntry.getJarFilter(); List aarFilter = classPathEntry.getAarFilter(); List warFilter = classPathEntry.getWarFilter(); List earFilter = classPathEntry.getEarFilter(); List jmodFilter = classPathEntry.getJmodFilter(); List zipFilter = classPathEntry.getZipFilter(); // Add a renaming filter, if specified. if (filter != null) { WildcardManager wildcardManager = new WildcardManager(); StringFunction nameFunction = new ListFunctionParser( new SingleFunctionParser( new FileNameParser(wildcardManager), wildcardManager)).parse(filter); reader = new RenamedDataEntryReader(nameFunction, reader); } // Unzip any apks, if necessary. reader = wrapInJarReader(reader, false, false, isApk, apkFilter, ".apk"); if (!isApk) { // Unzip any aabs, if necessary. reader = wrapInJarReader(reader, false, false, isAab, aabFilter, ".aab"); if (!isAab) { // Unzip any jars, if necessary. reader = wrapInJarReader(reader, false, false, isJar, jarFilter, ".jar"); if (!isJar) { // Unzip any aars, if necessary. reader = wrapInJarReader(reader, false, false, isAar, aarFilter, ".aar"); if (!isAar) { // Unzip any wars, if necessary. reader = wrapInJarReader(reader, true, false, isWar, warFilter, ".war"); if (!isWar) { // Unzip any ears, if necessary. reader = wrapInJarReader(reader, false, false, isEar, earFilter, ".ear"); if (!isEar) { // Unzip any jmods, if necessary. reader = wrapInJarReader(reader, true, true, isJmod, jmodFilter, ".jmod"); if (!isJmod) { // Unzip any zips, if necessary. reader = wrapInJarReader(reader, false, false, isZip, zipFilter, ".zip"); } } } } } } } return reader; } /** * Wraps the given DataEntryReader in a JarReader, filtering it if * necessary. * @param reader the data entry reader that can read the * entries contained in the jar file. * @param stripClassesPrefix specifies whether to strip the ""classes/" * prefix from contained .class data entries. *@param stripJmodHeader specifies whether to strip the jmod magic * bytes from the zip. * @param isJar specifies whether the data entries should * always be unzipped. * @param jarFilter otherwise, an optional filter on the data * entry names. * @param jarExtension also otherwise, a required data entry name * extension. * @return a DataEntryReader for reading the entries of jar file data * entries. */ private DataEntryReader wrapInJarReader(DataEntryReader reader, boolean stripClassesPrefix, boolean stripJmodHeader, boolean isJar, List jarFilter, String jarExtension) { if (stripClassesPrefix) { reader = new FilteredDataEntryReader( new DataEntryNameFilter(new ExtensionMatcher(CLASS_FILE_EXTENSION)), new PrefixStrippingDataEntryReader(JMOD_CLASS_FILE_PREFIX, reader), reader); } // Unzip any jars, if necessary. DataEntryReader jarReader = new JarReader(stripJmodHeader, reader); if (isJar) { // Always unzip. return jarReader; } else { // Add a filter, if specified. if (jarFilter != null) { jarReader = new FilteredDataEntryReader( new DataEntryNameFilter( new ListParser(new FileNameParser()).parse(jarFilter)), jarReader); } StringMatcher jarMatcher = new ExtensionMatcher(jarExtension); // Don't unzip archives in Android assets directories. if (android) { jarMatcher = new AndMatcher( new AndMatcher( new NotMatcher( new ListParser(new FileNameParser()).parse("assets/**,*/assets/**")), new NotMatcher( new ListParser(new FileNameParser()).parse("res/**,*/res/**"))), jarMatcher); } // Only unzip the right type of jars. return new FilteredDataEntryReader( new DataEntryNameFilter(jarMatcher), jarReader, reader); } } /** * Method to return an augmented filter for supported features. *

* Currently versioned class files (a feature introduced in Java 9) are not fully * supported by ProGuard. Only 1 version of a class can be read and processed. * If no custom filter targeting a specific version is used, exclude such classes * from being read. */ public static List getFilterExcludingVersionedClasses(ClassPathEntry classPathEntry) { List originalFilter = classPathEntry.getFilter(); if (originalFilter == null) { return Collections.singletonList(VERSIONS_EXCLUDE); } else { // If there is already a custom filter for versioned classes // assume that the filter is properly setup. for (String element : originalFilter) { if (element.contains(VERSIONS_PATTERN)) { return originalFilter; } } // Otherwise, exclude all versioned classes. List filter = new ArrayList<>(); filter.add(VERSIONS_EXCLUDE); filter.addAll(originalFilter); return filter; } } } ================================================ FILE: base/src/main/java/proguard/DataEntryWriterFactory.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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; import proguard.classfile.*; import proguard.io.*; import proguard.resources.file.ResourceFilePool; import proguard.util.*; import java.io.File; import java.security.KeyStore; import java.util.*; import java.util.function.Function; import static proguard.classfile.ClassConstants.CLASS_FILE_EXTENSION; /** * This class can create DataEntryWriter instances based on class paths. The * writers will wrap the output in the proper apks, jars, aars, wars, ears, * and zips. * * @author Eric Lafortune */ public class DataEntryWriterFactory { private static final boolean ENABLE_ZIP64_SUPPORT = System.getProperty("enable.zip64.support") != null; private static final String CLASS_FILE_PATTERN = "**.class"; private static final String CLASS_FILE_PREFIX = "classes/"; private static final byte[] JMOD_HEADER = new byte[] { 'J', 'M', 1, 0 }; private static final int PAGE_ALIGNMENT = 4096; private /*static*/ final String[][] JMOD_PREFIXES = new String[][] { { CLASS_FILE_PATTERN, CLASS_FILE_PREFIX } }; private /*static*/ final String[][] WAR_PREFIXES = new String[][] { { CLASS_FILE_PATTERN, CLASS_FILE_PREFIX } }; private final ClassPool programClassPool; private final ResourceFilePool resourceFilePool; private final int modificationTime; private final StringMatcher uncompressedFilter; private final int uncompressedAlignment; private final boolean pageAlignNativeLibs; private final boolean mergeAarJars; private final KeyStore.PrivateKeyEntry[] privateKeyEntries; private final Map jarWriterCache = new HashMap<>(); private final Function alternativeClassDataEntryWriterProvider; /** * Creates a new DataEntryWriterFactory. * * @param programClassPool the program class pool to process. * @param resourceFilePool the resource file pool to process. * @param modificationTime the modification date and time of * the zip entries, in DOS * format. * @param uncompressedFilter an optional filter for files that * should not be compressed. * @param uncompressedAlignment the desired alignment for the data * of uncompressed entries. * @param pageAlignNativeLibs specifies whether to align native * libraries at page boundaries. * @param mergeAarJars specifies whether to merge all jars * in an Android library aar into a * single jar. * @param privateKeyEntries optional private keys to sign jars. */ public DataEntryWriterFactory(ClassPool programClassPool, ResourceFilePool resourceFilePool, int modificationTime, StringMatcher uncompressedFilter, int uncompressedAlignment, boolean pageAlignNativeLibs, boolean mergeAarJars, KeyStore.PrivateKeyEntry[] privateKeyEntries) { this( programClassPool, resourceFilePool, modificationTime, uncompressedFilter, uncompressedAlignment, pageAlignNativeLibs, mergeAarJars, privateKeyEntries, null ); } /** * Creates a new DataEntryWriterFactory. * * @param programClassPool the program class pool to process. * @param resourceFilePool the resource file pool to process. * @param modificationTime the modification date and time of * the zip entries, in DOS * format. * @param uncompressedFilter an optional filter for files that * should not be compressed. * @param uncompressedAlignment the desired alignment for the data * of uncompressed entries. * @param pageAlignNativeLibs specifies whether to align native * libraries at page boundaries. * @param mergeAarJars specifies whether to merge all jars * in an Android app bundle into a * single jar. * @param privateKeyEntries optional private keys to sign jars. * @param alternativeClassDataEntryWriterProvider optional, to provide an alternative class writer, * instead of the default {@link ClassDataEntryWriter}. */ public DataEntryWriterFactory(ClassPool programClassPool, ResourceFilePool resourceFilePool, int modificationTime, StringMatcher uncompressedFilter, int uncompressedAlignment, boolean pageAlignNativeLibs, boolean mergeAarJars, KeyStore.PrivateKeyEntry[] privateKeyEntries, Function alternativeClassDataEntryWriterProvider) { this.programClassPool = programClassPool; this.resourceFilePool = resourceFilePool; this.modificationTime = modificationTime; this.uncompressedFilter = uncompressedFilter; this.uncompressedAlignment = uncompressedAlignment; this.pageAlignNativeLibs = pageAlignNativeLibs; this.mergeAarJars = mergeAarJars; this.privateKeyEntries = privateKeyEntries; this.alternativeClassDataEntryWriterProvider = alternativeClassDataEntryWriterProvider; } /** * Creates a DataEntryWriter that can write to the given class path entries. * * @param classPath the output class path. * @param fromIndex the start index in the class path. * @param toIndex the end index in the class path. * @param extraDataEntryWriter a writer to which extra injected files can be written. * @return a DataEntryWriter for writing to the given class path entries. */ public DataEntryWriter createDataEntryWriter(ClassPath classPath, int fromIndex, int toIndex, DataEntryWriter extraDataEntryWriter) { DataEntryWriter writer = null; // Create a chain of writers, one for each class path entry. for (int index = toIndex - 1; index >= fromIndex; index--) { ClassPathEntry entry = classPath.get(index); // We're allowing the same output file to be specified multiple // times in the class path. We only add a control manifest for // the input of the first occurrence. boolean addCheckingJarWriter = !outputFileOccurs(entry, classPath, 0, index); // We're allowing the same output file to be specified multiple // times in the class path. We only close cached jar writers // for this entry if its file doesn't occur again later on. boolean closeCachedJarWriter = !outputFileOccurs(entry, classPath, index + 1, classPath.size()); writer = createClassPathEntryWriter(entry, writer, extraDataEntryWriter, addCheckingJarWriter, closeCachedJarWriter); } return writer; } private boolean outputFileOccurs(ClassPathEntry entry, ClassPath classPath, int startIndex, int endIndex) { File file = entry.getFile(); for (int index = startIndex; index < endIndex; index++) { ClassPathEntry classPathEntry = classPath.get(index); if (classPathEntry.isOutput() && classPathEntry.getFile().equals(file)) { return true; } } return false; } /** * Creates a DataEntryWriter that can write to the given class path entry, * or delegate to another DataEntryWriter if its filters don't match. */ private DataEntryWriter createClassPathEntryWriter(ClassPathEntry classPathEntry, DataEntryWriter alternativeWriter, DataEntryWriter extraDataEntryWriter, boolean addCheckingJarWriter, boolean closeCachedJarWriter) { File file = classPathEntry.getFile(); boolean isDex = classPathEntry.isDex(); boolean isApk = classPathEntry.isApk(); boolean isAab = classPathEntry.isAab(); boolean isJar = classPathEntry.isJar(); boolean isAar = classPathEntry.isAar(); boolean isWar = classPathEntry.isWar(); boolean isEar = classPathEntry.isEar(); boolean isJmod = classPathEntry.isJmod(); boolean isZip = classPathEntry.isZip(); boolean isFile = isDex || isApk || isAab || isJar || isAar || isWar || isEar || isJmod || isZip; List filter = DataEntryReaderFactory.getFilterExcludingVersionedClasses(classPathEntry); List apkFilter = classPathEntry.getApkFilter(); List aabFilter = classPathEntry.getAabFilter(); List jarFilter = classPathEntry.getJarFilter(); List aarFilter = classPathEntry.getAarFilter(); List warFilter = classPathEntry.getWarFilter(); List earFilter = classPathEntry.getEarFilter(); List jmodFilter = classPathEntry.getJmodFilter(); List zipFilter = classPathEntry.getZipFilter(); // Create the writer for the main file or directory. DataEntryWriter writer = isFile ? new FixedFileWriter(file) : new DirectoryWriter(file); if (isDex) { // A dex file can't contain resource files. writer = new FilteredDataEntryWriter( new DataEntryNameFilter( new ExtensionMatcher(".dex")), writer); } else { // If the output is an archive, we'll flatten (unpack the contents of) // higher level input archives, e.g. when writing into a jar file, we // flatten zip files. boolean flattenApks = false; boolean flattenAabs = flattenApks || isApk; boolean flattenJars = flattenAabs || isAab; boolean flattenAars = flattenJars || isJar; boolean flattenWars = flattenAars || isAar; boolean flattenEars = flattenWars || isWar; boolean flattenJmods = flattenEars || isEar; boolean flattenZips = flattenJmods || isJmod; // Set up the filtered jar writers. writer = wrapInJarWriter(file, writer, extraDataEntryWriter, closeCachedJarWriter, flattenZips, isZip, false, ".zip", zipFilter, null, false, null); writer = wrapInJarWriter(file, writer, extraDataEntryWriter, closeCachedJarWriter, flattenJmods, isJmod, false, ".jmod", jmodFilter, JMOD_HEADER, false, JMOD_PREFIXES); writer = wrapInJarWriter(file, writer, extraDataEntryWriter, closeCachedJarWriter, flattenEars, isEar, false, ".ear", earFilter, null, false, null); writer = wrapInJarWriter(file, writer, extraDataEntryWriter, closeCachedJarWriter, flattenWars, isWar, false, ".war", warFilter, null, false, WAR_PREFIXES); writer = wrapInJarWriter(file, writer, extraDataEntryWriter, closeCachedJarWriter, flattenAars, isAar, false, ".aar", aarFilter, null, false, null); if (isAar) { // If we're writing an AAR, all input jars need to // be merged into a final classes.jar file or need to be put in the lib folder. if (mergeAarJars) { writer = new FilteredDataEntryWriter(new DataEntryNameFilter(new ExtensionMatcher(".jar")), new RenamedDataEntryWriter( new ConstantStringFunction("classes.jar"), writer), writer); } else { writer = new FilteredDataEntryWriter(new DataEntryNameFilter(new ExtensionMatcher(".jar")), new RenamedDataEntryWriter(string -> { String fileName = string.substring(string.lastIndexOf('/') + 1); if (fileName.equals("classes.jar")) { return fileName; } else { return "libs/" + fileName; } }, writer), writer); } } writer = wrapInJarWriter(file, writer, extraDataEntryWriter, closeCachedJarWriter, flattenJars, isJar, false, ".jar", jarFilter, null, false, null); // Either we create an aab or apk; they can not be nested. writer = isAab ? wrapInJarWriter(file, writer, extraDataEntryWriter, closeCachedJarWriter, flattenAabs, isAab, true, ".aab", aabFilter, null, false, null) : wrapInJarWriter(file, writer, extraDataEntryWriter, closeCachedJarWriter, flattenApks, isApk, false, ".apk", apkFilter, null, pageAlignNativeLibs, null); // Create a writer for plain class files. Don't close the enclosed // writer through it, but let it be closed later on. DataEntryWriter classWriter = new ClassDataEntryWriter(programClassPool, new NonClosingDataEntryWriter(writer)); // Add a renaming filter, if specified. if (filter != null) { WildcardManager wildcardManager = new WildcardManager(); StringFunction fileNameFunction = new ListFunctionParser( new SingleFunctionParser( new FileNameParser(wildcardManager), wildcardManager)).parse(filter); // Slight asymmetry: we filter plain class files beforehand, // but we filter and rename dex files and resource files after // creating and renaming them in the feature structure. // We therefore don't filter class files that go into dex // files. classWriter = new RenamedDataEntryWriter(fileNameFunction, classWriter); writer = new RenamedDataEntryWriter(fileNameFunction, writer); } writer = // Filter on class files. new NameFilteredDataEntryWriter( new ExtensionMatcher(CLASS_FILE_EXTENSION), alternativeClassDataEntryWriterProvider != null ? alternativeClassDataEntryWriterProvider.apply(writer) : classWriter, writer); } // Let the writer cascade, if specified. return alternativeWriter != null ? new CascadingDataEntryWriter(writer, alternativeWriter) : writer; } /** * Wraps the given DataEntryWriter in a ZipWriter, filtering and signing * if necessary. */ private DataEntryWriter wrapInJarWriter(File file, DataEntryWriter writer, DataEntryWriter extraDataEntryWriter, boolean closeCachedJarWriter, boolean flatten, boolean isJar, boolean isAab, String jarFilterExtension, List jarFilter, byte[] jarHeader, boolean pageAlignNativeLibs, String[][] prefixes) { StringMatcher pageAlignmentFilter = pageAlignNativeLibs ? new FileNameParser().parse("lib/*/*.so") : null; // Flatten jars or zip them up. DataEntryWriter zipWriter; if (flatten) { // Unpack the jar. zipWriter = new ParentDataEntryWriter(writer); } // Do we have a cached writer? else if (!isJar || (zipWriter = jarWriterCache.get(file)) == null) { // Sign the jar. zipWriter = wrapInSignedJarWriter(writer, extraDataEntryWriter, isAab, jarHeader, pageAlignmentFilter); // Add a prefix to specified files inside the jar. if (prefixes != null) { DataEntryWriter prefixlessJarWriter = zipWriter; for (int index = prefixes.length - 1; index >= 0; index--) { String prefixFileNameFilter = prefixes[index][0]; String prefix = prefixes[index][1]; zipWriter = new FilteredDataEntryWriter( new DataEntryNameFilter( new ListParser(new FileNameParser()).parse(prefixFileNameFilter)), new PrefixAddingDataEntryWriter(prefix, prefixlessJarWriter), zipWriter); } } // Is it an outermost archive? if (isJar) { // Cache the jar writer so it can be reused. jarWriterCache.put(file, zipWriter); } } // Only close an outermost archive if specified. // It may be used later on. if (isJar && !closeCachedJarWriter) { zipWriter = new NonClosingDataEntryWriter(zipWriter); } // Either zip up the jar or delegate to the original writer. return // Is the data entry part of the specified type of jar? new FilteredDataEntryWriter( new DataEntryParentFilter( new DataEntryNameFilter( new ExtensionMatcher(jarFilterExtension))), // The parent of the data entry is a jar. // Write the data entry to the jar. // Apply the jar filter, if specified, to the parent. jarFilter != null ? new FilteredDataEntryWriter( new DataEntryParentFilter( new DataEntryNameFilter( new ListParser(new FileNameParser()).parse(jarFilter))), zipWriter) : zipWriter, // The parent of the data entry is not a jar. // Write the entry to a jar anyway if the output is a jar. // Otherwise just delegate to the original writer. isJar ? zipWriter : writer); } /** * Wraps the given DataEntryWriter in a ZipWriter, signing if necessary. */ private DataEntryWriter wrapInSignedJarWriter(DataEntryWriter writer, DataEntryWriter extraDataEntryWriter, boolean isAab, byte[] jarHeader, StringMatcher pageAlignmentFilter) { // Pack the zip. DataEntryWriter zipWriter = new ZipWriter(uncompressedFilter, uncompressedAlignment, ENABLE_ZIP64_SUPPORT, pageAlignmentFilter, PAGE_ALIGNMENT, modificationTime, jarHeader, writer); // Do we need to sign the jar? if (privateKeyEntries != null) { // Sign the jar (signature scheme v1). zipWriter = new SignedJarWriter(privateKeyEntries[0], new String[] { JarWriter.DEFAULT_DIGEST_ALGORITHM }, ProGuard.VERSION, null, zipWriter); } return zipWriter; } } ================================================ FILE: base/src/main/java/proguard/DescriptorKeepChecker.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.optimize.*; import java.util.List; /** * This class checks whether classes referenced by class members that are * marked to be kept are marked to be kept too. * * @author Eric Lafortune */ public class DescriptorKeepChecker implements MemberVisitor, ClassVisitor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final WarningPrinter notePrinter; // Some fields acting as parameters for the class visitor. private Clazz referencingClass; private Member referencingMember; private boolean isField; /** * Creates a new DescriptorKeepChecker. */ public DescriptorKeepChecker(ClassPool programClassPool, ClassPool libraryClassPool, WarningPrinter notePrinter) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.notePrinter = notePrinter; } /** * Checks the classes mentioned in the given keep specifications, printing * notes if necessary. */ public void checkClassSpecifications(List keepSpecifications) { // Clean up any old processing info. programClassPool.classesAccept(new ClassCleaner()); libraryClassPool.classesAccept(new ClassCleaner()); // Create a visitor for marking the seeds. KeepMarker keepMarker = new KeepMarker(); ClassPoolVisitor classPoolvisitor = new KeepClassSpecificationVisitorFactory(true, true, true) .createClassPoolVisitor(keepSpecifications, keepMarker, keepMarker, keepMarker, null); // Mark the seeds. programClassPool.accept(classPoolvisitor); libraryClassPool.accept(classPoolvisitor); // Print out notes about argument types that are not being kept in // class members that are being kept. programClassPool.classesAccept( new AllMemberVisitor( new KeptMemberFilter(this))); } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { //referencingClass = programClass; //referencingMember = programField; //isField = true; // // Don't check the type, because it is not required for introspection. //programField.referencedClassesAccept(this); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { referencingClass = programClass; referencingMember = programMethod; isField = false; // Don't check the return type, because it is not required for // introspection (e.g. the return type of the special Enum methods). //programMethod.referencedClassesAccept(this); Clazz[] referencedClasses = programMethod.referencedClasses; if (referencedClasses != null) { int count = referencedClasses.length; // Adapt the count if the return type is a class type (not so // pretty; assuming test just checks for final ';'). if (ClassUtil.isInternalClassType(programMethod.getDescriptor(programClass))) { count--; } for (int index = 0; index < count; index++) { if (referencedClasses[index] != null) { referencedClasses[index].accept(this); } } } } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) {} @Override public void visitProgramClass(ProgramClass programClass) { if (!KeepMarker.isKept(programClass)) { notePrinter.print(referencingClass.getName(), programClass.getName(), "Note: the configuration keeps the entry point '" + ClassUtil.externalClassName(referencingClass.getName()) + " { " + (isField ? ClassUtil.externalFullFieldDescription(0, referencingMember.getName(referencingClass), referencingMember.getDescriptor(referencingClass)) : ClassUtil.externalFullMethodDescription(referencingClass.getName(), 0, referencingMember.getName(referencingClass), referencingMember.getDescriptor(referencingClass))) + "; }', but not the descriptor class '" + ClassUtil.externalClassName(programClass.getName()) + "'"); } } } ================================================ FILE: base/src/main/java/proguard/Dumper.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.visitor.*; import proguard.pass.Pass; import proguard.util.PrintWriterUtil; import java.io.PrintWriter; /** * This pass prints the contents of the program class pool. * * @author Tim Van Den Broecke */ public class Dumper implements Pass { private static final Logger logger = LogManager.getLogger(Dumper.class); private final Configuration configuration; public Dumper(Configuration configuration) { this.configuration = configuration; } @Override public void execute(AppView appView) throws Exception { logger.info("Printing classes to [{}]...", PrintWriterUtil.fileName(configuration.dump)); PrintWriter pw = PrintWriterUtil.createPrintWriterOut(configuration.dump); try { appView.programClassPool.classesAccept(new ClassPrinter(pw)); } finally { PrintWriterUtil.closePrintWriter(configuration.dump, pw); } } } ================================================ FILE: base/src/main/java/proguard/DuplicateClassPrinter.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor writes out notes about the class files that it visits * being duplicates. * * @author Eric Lafortune */ public class DuplicateClassPrinter implements ClassVisitor { private final WarningPrinter notePrinter; /** * Creates a new DuplicateClassVisitor. */ public DuplicateClassPrinter(WarningPrinter notePrinter) { this.notePrinter = notePrinter; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { notePrinter.print(programClass.getName(), "Note: duplicate definition of program class [" + ClassUtil.externalClassName(programClass.getName()) + "]"); } @Override public void visitLibraryClass(LibraryClass libraryClass) { notePrinter.print(libraryClass.getName(), "Note: duplicate definition of library class [" + ClassUtil.externalClassName(libraryClass.getName()) + "]"); } } ================================================ FILE: base/src/main/java/proguard/DuplicateResourceFilePrinter.java ================================================ /* * 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; import proguard.classfile.util.WarningPrinter; import proguard.resources.file.ResourceFile; import proguard.resources.file.visitor.ResourceFileVisitor; import proguard.resources.kotlinmodule.KotlinModule; /** * This ResourceFileVisitor writes out notes about the resource files that it visits * being duplicates. * * @author Thomas Neidhart */ public class DuplicateResourceFilePrinter implements ResourceFileVisitor { private final WarningPrinter notePrinter; /** * Creates a new DuplicateResourceFilePrinter. */ public DuplicateResourceFilePrinter(WarningPrinter notePrinter) { this.notePrinter = notePrinter; } // Implementations for ResourceFileVisitor. @Override public void visitResourceFile(ResourceFile resourceFile) { notePrinter.print(resourceFile.getFileName(), "Note: duplicate definition of resource file [" + resourceFile.getFileName() + "]"); } @Override public void visitKotlinModule(KotlinModule kotlinModule) { notePrinter.print(kotlinModule.getFileName(), "Note: duplicate definition of Kotlin module file [" + kotlinModule.getFileName() + "]"); } } ================================================ FILE: base/src/main/java/proguard/FileWordReader.java ================================================ /* * 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; import java.io.*; import java.net.URL; /** * A WordReader that returns words from a file or a URL. * * @author Eric Lafortune */ public class FileWordReader extends LineWordReader { /** * Creates a new FileWordReader for the given file. */ public FileWordReader(File file) throws IOException { super(new LineNumberReader( new BufferedReader( new InputStreamReader( new FileInputStream(file), "UTF-8"))), "file '" + file.getPath() + "'", file.getParentFile()); } /** * Creates a new FileWordReader for the given URL. */ public FileWordReader(URL url) throws IOException { super(new LineNumberReader( new BufferedReader( new InputStreamReader(url.openStream(), "UTF-8"))), "file '" + url.toString() + "'", url); } } ================================================ FILE: base/src/main/java/proguard/FullyQualifiedClassNameChecker.java ================================================ /* * 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import java.util.*; /** * This class checks if the user has forgotten to fully qualify any classes * in the configuration. * * @author Eric Lafortune */ public class FullyQualifiedClassNameChecker implements ClassVisitor { private static final Logger logger = LogManager.getLogger(FullyQualifiedClassNameChecker.class); private static final String INVALID_CLASS_EXTENSION = ClassUtil.internalClassName(ClassConstants.CLASS_FILE_EXTENSION); private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final WarningPrinter notePrinter; /** * Creates a new FullyQualifiedClassNameChecker. */ public FullyQualifiedClassNameChecker(ClassPool programClassPool, ClassPool libraryClassPool, WarningPrinter notePrinter) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.notePrinter = notePrinter; } /** * Checks the classes mentioned in the given class specifications, printing * notes if necessary. */ public void checkClassSpecifications(List classSpecifications) { if (classSpecifications != null) { for (int index = 0; index < classSpecifications.size(); index++) { ClassSpecification classSpecification = (ClassSpecification)classSpecifications.get(index); checkType(classSpecification.annotationType); checkClassName(classSpecification.className); checkType(classSpecification.extendsAnnotationType); checkClassName(classSpecification.extendsClassName); checkMemberSpecifications(classSpecification.fieldSpecifications, true); checkMemberSpecifications(classSpecification.methodSpecifications, false); } } } /** * Checks the classes mentioned in the given class member specifications, * printing notes if necessary. */ private void checkMemberSpecifications(List memberSpecifications, boolean isField) { if (memberSpecifications != null) { for (int index = 0; index < memberSpecifications.size(); index++) { MemberSpecification memberSpecification = (MemberSpecification)memberSpecifications.get(index); checkType(memberSpecification.annotationType); if (isField) { checkType(memberSpecification.descriptor); } else { checkDescriptor(memberSpecification.descriptor); } } } } /** * Checks the classes mentioned in the given class member descriptor, * printing notes if necessary. */ private void checkDescriptor(String descriptor) { if (descriptor != null) { InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); checkType(internalTypeEnumeration.returnType()); while (internalTypeEnumeration.hasMoreTypes()) { checkType(internalTypeEnumeration.nextType()); } } } /** * Checks the class mentioned in the given type (if any), * printing notes if necessary. */ private void checkType(String type) { if (type != null) { checkClassName(ClassUtil.internalClassNameFromType(type)); } } /** * Checks the specified class (if any), * printing notes if necessary. */ private void checkClassName(String className) { if (className != null && !containsWildCards(className) && programClassPool.getClass(className) == null && libraryClassPool.getClass(className) == null && notePrinter.accepts(className)) { notePrinter.print(className, "Note: the configuration refers to the unknown class '" + ClassUtil.externalClassName(className) + "'"); // Strip "/class" or replace the package name by a wildcard. int lastSeparatorIndex = className.lastIndexOf(TypeConstants.PACKAGE_SEPARATOR); String fullyQualifiedClassName = className.endsWith(INVALID_CLASS_EXTENSION) ? className.substring(0, lastSeparatorIndex) : "**" + TypeConstants.PACKAGE_SEPARATOR + className.substring(lastSeparatorIndex + 1); // Suggest matching classes. ClassNameFilter classNameFilter = new ClassNameFilter(fullyQualifiedClassName, this); programClassPool.classesAccept(classNameFilter); libraryClassPool.classesAccept(classNameFilter); } } private static boolean containsWildCards(String string) { return string != null && (string.indexOf('!') >= 0 || string.indexOf('*') >= 0 || string.indexOf('?') >= 0 || string.indexOf(',') >= 0 || string.indexOf("///") >= 0 || string.indexOf('<') >= 0); } // Implementations for ClassVisitor. public void visitAnyClass(Clazz clazz) { logger.info(" Maybe you meant the fully qualified name '{}'?", ClassUtil.externalClassName(clazz.getName())); } } ================================================ FILE: base/src/main/java/proguard/GPL.java ================================================ /* * 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.*; import java.util.*; /** * This class checks and prints out information about the GPL. * * @author Eric Lafortune */ public class GPL { private static final Logger logger = LogManager.getLogger(GPL.class); /** * Prints out a note about the GPL if ProGuard is linked against unknown * code. */ public static void check() { ByteArrayOutputStream out = new ByteArrayOutputStream(); new Exception().printStackTrace(new PrintStream(out)); LineNumberReader reader = new LineNumberReader( new InputStreamReader( new ByteArrayInputStream(out.toByteArray()))); Set unknownPackageNames = unknownPackageNames(reader); if (unknownPackageNames.size() > 0) { String uniquePackageNames = uniquePackageNames(unknownPackageNames); logger.info("ProGuard is released under the GNU General Public License. You therefore"); logger.info("must ensure that programs that link to it ({}...)", uniquePackageNames); logger.info("carry the GNU General Public License as well. Alternatively, you can"); logger.info("apply for an exception with the author of ProGuard."); } } /** * Returns a set of package names from the given stack trace. */ private static Set unknownPackageNames(LineNumberReader reader) { Set packageNames = new HashSet(); try { while (true) { String line = reader.readLine(); if (line == null) { break; } line = line.trim(); if (line.startsWith("at ")) { line = line.substring(2).trim(); line = trimSuffix(line, '('); line = trimSuffix(line, '.'); line = trimSuffix(line, '.'); if (line.length() > 0 && !isKnown(line)) { packageNames.add(line); } } } } catch (IOException ex) { // We'll just stop looking for more names. } return packageNames; } /** * Returns a comma-separated list of package names from the set, excluding * any subpackages of packages in the set. */ private static String uniquePackageNames(Set packageNames) { StringBuffer buffer = new StringBuffer(); Iterator iterator = packageNames.iterator(); while (iterator.hasNext()) { String packageName = (String)iterator.next(); if (!containsPrefix(packageNames, packageName)) { buffer.append(packageName).append(", "); } } return buffer.toString(); } /** * Returns a given string without the suffix, as defined by the given * separator. */ private static String trimSuffix(String string, char separator) { int index = string.lastIndexOf(separator); return index < 0 ? "" : string.substring(0, index); } /** * Returns whether the given set contains a prefix of the given name. */ private static boolean containsPrefix(Set set, String name) { int index = 0; while (!set.contains(name.substring(0, index))) { index = name.indexOf('.', index + 1); if (index < 0) { return false; } } return true; } /** * Returns whether the given package name has been granted an exception * against the GPL linking clause, by the copyright holder of ProGuard. * This method is not legally binding, but of course the actual license is. * Please contact the copyright holder if you would like an exception for * your code as well. */ private static boolean isKnown(String packageName) { return packageName.startsWith("java") || packageName.startsWith("jdk.internal.reflect") || packageName.startsWith("sun.reflect") || packageName.startsWith("proguard") || packageName.startsWith("org.apache.tools.ant") || packageName.startsWith("org.apache.tools.maven") || packageName.startsWith("org.gradle") || packageName.startsWith("org.codehaus.groovy") || packageName.startsWith("org.eclipse") || packageName.startsWith("org.netbeans") || packageName.startsWith("com.android") || packageName.startsWith("com.intel") || packageName.startsWith("com.sun.kvem") || packageName.startsWith("net.certiv.proguarddt") || packageName.startsWith("groovy") || packageName.startsWith("scala") || packageName.startsWith("sbt") || packageName.startsWith("xsbt") || packageName.startsWith("eclipseme"); } public static void main(String[] args) { LineNumberReader reader = new LineNumberReader( new InputStreamReader(System.in)); Set unknownPackageNames = unknownPackageNames(reader); if (unknownPackageNames.size() > 0) { String uniquePackageNames = uniquePackageNames(unknownPackageNames); System.out.println(uniquePackageNames); } } } ================================================ FILE: base/src/main/java/proguard/GetAnnotationChecker.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.util.*; /** * This constant visitor checks whether visited method references try to * access annotations. * * @author Eric Lafortune */ public class GetAnnotationChecker implements ConstantVisitor { private final WarningPrinter notePrinter; /** * Creates a new GetAnnotationChecker. */ public GetAnnotationChecker(WarningPrinter notePrinter) { this.notePrinter = notePrinter; } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { String className = methodrefConstant.getClassName(clazz); if (className.equals(ClassConstants.NAME_JAVA_LANG_CLASS) || className.equals(ClassConstants.NAME_JAVA_LANG_REFLECT_FIELD) || className.equals(ClassConstants.NAME_JAVA_LANG_REFLECT_METHOD)) { String methodName = methodrefConstant.getName(clazz); if (methodName.equals(ClassConstants.METHOD_NAME_GET_ANNOTATION) || methodName.equals(ClassConstants.METHOD_NAME_GET_ANNOTATIONS) || methodName.equals(ClassConstants.METHOD_NAME_GET_DECLARED_ANNOTATIONS) || methodName.equals(ClassConstants.METHOD_NAME_GET_PARAMETER_ANNOTATIONS)) { notePrinter.print(clazz.getName(), "Note: " + ClassUtil.externalClassName(clazz.getName()) + " calls '" + ClassUtil.externalShortClassName(ClassUtil.externalClassName(className)) + "." + methodName + "'"); } } } } ================================================ FILE: base/src/main/java/proguard/GetEnclosingClassChecker.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.util.*; /** * This constant visitor checks whether visited method references try to * access enclosing classes. * * @author Eric Lafortune */ public class GetEnclosingClassChecker implements ConstantVisitor { private final WarningPrinter notePrinter; /** * Creates a new GetEnclosingMethodChecker. */ public GetEnclosingClassChecker(WarningPrinter notePrinter) { this.notePrinter = notePrinter; } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { String className = methodrefConstant.getClassName(clazz); if (className.equals(ClassConstants.NAME_JAVA_LANG_CLASS)) { String methodName = methodrefConstant.getName(clazz); if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_ENCLOSING_CLASS) || methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_DECLARING_CLASS)) { notePrinter.print(clazz.getName(), "Note: " + ClassUtil.externalClassName(clazz.getName()) + " calls '" + ClassUtil.externalShortClassName(ClassUtil.externalClassName(className)) + "." + methodName + "'"); } } } } ================================================ FILE: base/src/main/java/proguard/GetEnclosingMethodChecker.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.util.*; /** * This constant visitor checks whether visited method references try to * access enclosing methods. * * @author Eric Lafortune */ public class GetEnclosingMethodChecker implements ConstantVisitor { private final WarningPrinter notePrinter; /** * Creates a new GetEnclosingMethodChecker. */ public GetEnclosingMethodChecker(WarningPrinter notePrinter) { this.notePrinter = notePrinter; } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { String className = methodrefConstant.getClassName(clazz); if (className.equals(ClassConstants.NAME_JAVA_LANG_CLASS)) { String methodName = methodrefConstant.getName(clazz); if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_ENCLOSING_CONSTRUCTOR) || methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_ENCLOSING_METHOD)) { notePrinter.print(clazz.getName(), "Note: " + ClassUtil.externalClassName(clazz.getName()) + " calls '" + ClassUtil.externalShortClassName(ClassUtil.externalClassName(className)) + "." + methodName + "'"); } } } } ================================================ FILE: base/src/main/java/proguard/GetSignatureChecker.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.util.*; /** * This constant visitor checks whether visited method references try to * access signatures. * * @author Eric Lafortune */ public class GetSignatureChecker implements ConstantVisitor { private final WarningPrinter notePrinter; /** * Creates a new GetSignatureChecker. */ public GetSignatureChecker(WarningPrinter notePrinter) { this.notePrinter = notePrinter; } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { String className = methodrefConstant.getClassName(clazz); if (className.equals(ClassConstants.NAME_JAVA_LANG_CLASS) || className.equals(ClassConstants.NAME_JAVA_LANG_REFLECT_FIELD) || className.equals(ClassConstants.NAME_JAVA_LANG_REFLECT_METHOD)) { String methodName = methodrefConstant.getName(clazz); if (methodName.startsWith(ClassConstants.METHOD_NAME_GET_TYPE_PREFIX) || methodName.startsWith(ClassConstants.METHOD_NAME_GET_GENERIC_PREFIX)) { notePrinter.print(clazz.getName(), "Note: " + ClassUtil.externalClassName(clazz.getName()) + " calls '" + ClassUtil.externalShortClassName(ClassUtil.externalClassName(className)) + "." + methodName + "'"); } } } } ================================================ FILE: base/src/main/java/proguard/Initializer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.visitor.AllConstantVisitor; import proguard.classfile.instruction.visitor.AllInstructionVisitor; import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.pass.Pass; import proguard.resources.file.visitor.ResourceJavaReferenceClassInitializer; import proguard.resources.kotlinmodule.util.KotlinModuleReferenceInitializer; import proguard.util.*; import java.io.*; import java.util.*; /** * This pass initializes class pools and resource information. * * @author Eric Lafortune */ public class Initializer implements Pass { private static final Logger logger = LogManager.getLogger(Initializer.class); private final Configuration configuration; public Initializer(Configuration configuration) { this.configuration = configuration; } /** * Initializes the classes in the given program class pool and library class * pool, performs some basic checks, and shrinks the library class pool. */ @Override public void execute(AppView appView) throws IOException { logger.info("Initializing..."); boolean checkConfiguration = configuration.shrink || configuration.optimize || configuration.obfuscate; int originalLibraryClassPoolSize = appView.libraryClassPool.size(); // Perform basic checks on the configuration. WarningPrinter fullyQualifiedClassNameNotePrinter = new WarningLogger(logger, configuration.note); if (checkConfiguration) { FullyQualifiedClassNameChecker fullyQualifiedClassNameChecker = new FullyQualifiedClassNameChecker(appView.programClassPool, appView.libraryClassPool, fullyQualifiedClassNameNotePrinter); fullyQualifiedClassNameChecker.checkClassSpecifications(configuration.keep); fullyQualifiedClassNameChecker.checkClassSpecifications(configuration.assumeNoSideEffects); fullyQualifiedClassNameChecker.checkClassSpecifications(configuration.assumeNoExternalSideEffects); fullyQualifiedClassNameChecker.checkClassSpecifications(configuration.assumeNoEscapingParameters); fullyQualifiedClassNameChecker.checkClassSpecifications(configuration.assumeNoExternalReturnValues); } StringMatcher keepAttributesMatcher = configuration.keepAttributes != null ? new ListParser(new NameParser()).parse(configuration.keepAttributes) : new EmptyStringMatcher(); WarningPrinter getAnnotationNotePrinter = new WarningLogger(logger, configuration.note); if (!keepAttributesMatcher.matches(Attribute.RUNTIME_VISIBLE_ANNOTATIONS)) { appView.programClassPool.classesAccept( new AllConstantVisitor( new GetAnnotationChecker(getAnnotationNotePrinter))); } WarningPrinter getSignatureNotePrinter = new WarningLogger(logger, configuration.note); if (!keepAttributesMatcher.matches(Attribute.SIGNATURE)) { appView.programClassPool.classesAccept( new AllConstantVisitor( new GetSignatureChecker(getSignatureNotePrinter))); } WarningPrinter getEnclosingClassNotePrinter = new WarningLogger(logger, configuration.note); if (!keepAttributesMatcher.matches(Attribute.INNER_CLASSES)) { appView.programClassPool.classesAccept( new AllConstantVisitor( new GetEnclosingClassChecker(getEnclosingClassNotePrinter))); } WarningPrinter getEnclosingMethodNotePrinter = new WarningLogger(logger, configuration.note); if (!keepAttributesMatcher.matches(Attribute.ENCLOSING_METHOD)) { appView.programClassPool.classesAccept( new AllConstantVisitor( new GetEnclosingMethodChecker(getEnclosingMethodNotePrinter))); } // Construct a reduced library class pool with only those library // classes whose hierarchies are referenced by the program classes. // We can't do this if we later have to come up with the obfuscated // class member names that are globally unique. ClassPool reducedLibraryClassPool = configuration.useUniqueClassMemberNames ? null : new ClassPool(); WarningPrinter classReferenceWarningPrinter = new WarningLogger(logger, configuration.warn); WarningPrinter dependencyWarningPrinter = new WarningLogger(logger, configuration.warn); // Initialize the superclass hierarchies for program classes. appView.programClassPool.classesAccept( new ClassSuperHierarchyInitializer(appView.programClassPool, appView.libraryClassPool, classReferenceWarningPrinter, null)); // Initialize the superclass hierarchy of all library classes, without // warnings. appView.libraryClassPool.classesAccept( new ClassSuperHierarchyInitializer(appView.programClassPool, appView.libraryClassPool, null, dependencyWarningPrinter)); // Initialize the class references of program class members and // attributes. Note that all superclass hierarchies have to be // initialized for this purpose. WarningPrinter programMemberReferenceWarningPrinter = new WarningLogger(logger, configuration.warn); WarningPrinter libraryMemberReferenceWarningPrinter = new WarningLogger(logger, configuration.warn); appView.programClassPool.classesAccept( new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool, classReferenceWarningPrinter, programMemberReferenceWarningPrinter, libraryMemberReferenceWarningPrinter, null)); if (reducedLibraryClassPool != null) { if (configuration.keepKotlinMetadata) { // TODO(T16917): Improve this, so that only relevant classes are kept. appView.libraryClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( (clazz, kotlinMetadata) -> clazz.accept(new ClassPoolFiller(reducedLibraryClassPool)) ) ); } // Collect the library classes that are directly referenced by // program classes, without reflection. appView.programClassPool.classesAccept( new ReferencedClassVisitor(true, new LibraryClassFilter( new ClassPoolFiller(reducedLibraryClassPool)))); // Reinitialize the superclass hierarchies of referenced library // classes, this time with warnings. reducedLibraryClassPool.classesAccept( new ClassSuperHierarchyInitializer(appView.programClassPool, appView.libraryClassPool, classReferenceWarningPrinter, null)); } // Initialize the enum annotation references. appView.programClassPool.classesAccept( new AllAttributeVisitor(true, new AllElementValueVisitor(true, new EnumFieldReferenceInitializer()))); // Initialize the Class.forName references. WarningPrinter dynamicClassReferenceNotePrinter = new WarningLogger(logger, configuration.note); WarningPrinter classForNameNotePrinter = new WarningLogger(logger, configuration.note); appView.programClassPool.classesAccept( new AllMethodVisitor( new AllAttributeVisitor( new AllInstructionVisitor( new DynamicClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool, dynamicClassReferenceNotePrinter, null, classForNameNotePrinter, createClassNoteExceptionMatcher(configuration.keep, true)))))); // Initialize the Class.get[Declared]{Field,Method} references. WarningPrinter getMemberNotePrinter = new WarningLogger(logger, configuration.note); appView.programClassPool.classesAccept( new AllMethodVisitor( new AllAttributeVisitor( new DynamicMemberReferenceInitializer(appView.programClassPool, appView.libraryClassPool, getMemberNotePrinter, createClassMemberNoteExceptionMatcher(configuration.keep, true), createClassMemberNoteExceptionMatcher(configuration.keep, false))))); // Initialize other string constant references, if requested. if (configuration.adaptClassStrings != null) { appView.programClassPool.classesAccept( new ClassNameFilter(configuration.adaptClassStrings, new AllConstantVisitor( new StringReferenceInitializer(appView.programClassPool, appView.libraryClassPool)))); } // Initialize the class references of library class members. if (reducedLibraryClassPool != null) { // Collect the library classes that are referenced by program // classes, directly or indirectly, with or without reflection. appView.programClassPool.classesAccept( new ReferencedClassVisitor( new LibraryClassFilter( new ClassHierarchyTraveler(true, true, true, false, new LibraryClassFilter( new ClassPoolFiller(reducedLibraryClassPool)))))); // Initialize the class references of referenced library // classes, without warnings. reducedLibraryClassPool.classesAccept( new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool, null, null, null, dependencyWarningPrinter)); // Reset the library class pool. appView.libraryClassPool.clear(); // Copy the library classes that are referenced directly by program // classes and the library classes that are referenced by referenced // library classes. reducedLibraryClassPool.classesAccept( new MultiClassVisitor( new ClassHierarchyTraveler(true, true, true, false, new LibraryClassFilter( new ClassPoolFiller(appView.libraryClassPool))), new ReferencedClassVisitor(true, new LibraryClassFilter( new ClassHierarchyTraveler(true, true, true, false, new LibraryClassFilter( new ClassPoolFiller(appView.libraryClassPool))))) )); } else { // Initialize the class references of all library class members. appView.libraryClassPool.classesAccept( new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool, null, null, null, dependencyWarningPrinter)); } // Initialize the subclass hierarchies (in the right order, // with a single instance). ClassSubHierarchyInitializer classSubHierarchyInitializer = new ClassSubHierarchyInitializer(); appView.programClassPool.accept(classSubHierarchyInitializer); appView.libraryClassPool.accept(classSubHierarchyInitializer); if (configuration.keepKotlinMetadata) { appView.resourceFilePool.resourceFilesAccept(new KotlinModuleReferenceInitializer(appView.programClassPool, appView.libraryClassPool)); } // Share strings between the classes, to reduce heap memory usage. appView.programClassPool.classesAccept(new StringSharer()); appView.libraryClassPool.classesAccept(new StringSharer()); // Check for any unmatched class members. WarningPrinter classMemberNotePrinter = new WarningLogger(logger, configuration.note); if (checkConfiguration) { ClassMemberChecker classMemberChecker = new ClassMemberChecker(appView.programClassPool, classMemberNotePrinter); classMemberChecker.checkClassSpecifications(configuration.keep); classMemberChecker.checkClassSpecifications(configuration.assumeNoSideEffects); classMemberChecker.checkClassSpecifications(configuration.assumeNoExternalSideEffects); classMemberChecker.checkClassSpecifications(configuration.assumeNoEscapingParameters); classMemberChecker.checkClassSpecifications(configuration.assumeNoExternalReturnValues); } // Check for unkept descriptor classes of kept class members. WarningPrinter descriptorKeepNotePrinter = new WarningLogger(logger, configuration.note); if (checkConfiguration) { new DescriptorKeepChecker(appView.programClassPool, appView.libraryClassPool, descriptorKeepNotePrinter).checkClassSpecifications(configuration.keep); } // Check for keep options that only match library classes. WarningPrinter libraryKeepNotePrinter = new WarningLogger(logger, configuration.note); if (checkConfiguration) { new LibraryKeepChecker(appView.programClassPool, appView.libraryClassPool, libraryKeepNotePrinter).checkClassSpecifications(configuration.keep); } // Initialize the references to Java classes in resource files. appView.resourceFilePool.resourceFilesAccept( new ResourceJavaReferenceClassInitializer(appView.programClassPool)); // Print out a summary of the notes, if necessary. int fullyQualifiedNoteCount = fullyQualifiedClassNameNotePrinter.getWarningCount(); if (fullyQualifiedNoteCount > 0) { logger.info("Note: there were {} references to unknown classes.", fullyQualifiedNoteCount); logger.info(" You should check your configuration for typos."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#unknownclass)"); } int classMemberNoteCount = classMemberNotePrinter.getWarningCount(); if (classMemberNoteCount > 0) { logger.info("Note: there were {} references to unknown class members.", classMemberNoteCount); logger.info(" You should check your configuration for typos."); } int getAnnotationNoteCount = getAnnotationNotePrinter.getWarningCount(); if (getAnnotationNoteCount > 0) { logger.info("Note: there were {} classes trying to access annotations using reflection.", getAnnotationNoteCount); logger.info(" You should consider keeping the annotation attributes"); logger.info(" (using '-keepattributes *Annotation*')."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#attributes)"); } int getSignatureNoteCount = getSignatureNotePrinter.getWarningCount(); if (getSignatureNoteCount > 0) { logger.info("Note: there were {} classes trying to access generic signatures using reflection.", getSignatureNoteCount); logger.info(" You should consider keeping the signature attributes"); logger.info(" (using '-keepattributes Signature')."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#attributes)"); } int getEnclosingClassNoteCount = getEnclosingClassNotePrinter.getWarningCount(); if (getEnclosingClassNoteCount > 0) { logger.info("Note: there were {} classes trying to access enclosing classes using reflection.", getEnclosingClassNoteCount); logger.info(" You should consider keeping the inner classes attributes"); logger.info(" (using '-keepattributes InnerClasses')."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#attributes)"); } int getEnclosingMethodNoteCount = getEnclosingMethodNotePrinter.getWarningCount(); if (getEnclosingMethodNoteCount > 0) { logger.info("Note: there were {} classes trying to access enclosing methods using reflection.", getEnclosingMethodNoteCount); logger.info(" You should consider keeping the enclosing method attributes"); logger.info(" (using '-keepattributes InnerClasses,EnclosingMethod')."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#attributes)"); } int descriptorNoteCount = descriptorKeepNotePrinter.getWarningCount(); if (descriptorNoteCount > 0) { logger.info("Note: there were {} unkept descriptor classes in kept class members.", descriptorNoteCount); logger.info(" You should consider explicitly keeping the mentioned classes"); logger.info(" (using '-keep')."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#descriptorclass)"); } int libraryNoteCount = libraryKeepNotePrinter.getWarningCount(); if (libraryNoteCount > 0) { logger.info("Note: there were {} library classes explicitly being kept.", libraryNoteCount); logger.info(" You don't need to keep library classes; they are already left unchanged."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#libraryclass)"); } int dynamicClassReferenceNoteCount = dynamicClassReferenceNotePrinter.getWarningCount(); if (dynamicClassReferenceNoteCount > 0) { logger.info("Note: there were {} unresolved dynamic references to classes or interfaces.", dynamicClassReferenceNoteCount); logger.info(" You should check if you need to specify additional program jars."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#dynamicalclass)"); } int classForNameNoteCount = classForNameNotePrinter.getWarningCount(); if (classForNameNoteCount > 0) { logger.info("Note: there were {} class casts of dynamically created class instances.", classForNameNoteCount); logger.info(" You might consider explicitly keeping the mentioned classes and/or"); logger.info(" their implementations (using '-keep')."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#dynamicalclasscast)"); } int getmemberNoteCount = getMemberNotePrinter.getWarningCount(); if (getmemberNoteCount > 0) { logger.info("Note: there were {} accesses to class members by means of reflection.", getmemberNoteCount); logger.info(" You should consider explicitly keeping the mentioned class members"); logger.info(" (using '-keep' or '-keepclassmembers')."); logger.info(" (https://www.guardsquare.com/proguard/manual/troubleshooting#dynamicalclassmember)"); } // Print out a summary of the warnings, if necessary. int classReferenceWarningCount = classReferenceWarningPrinter.getWarningCount(); if (classReferenceWarningCount > 0) { logger.warn("Warning: there were {} unresolved references to classes or interfaces.", classReferenceWarningCount); logger.warn(" You may need to add missing library jars or update their versions."); logger.warn(" If your code works fine without the missing classes, you can suppress"); logger.warn(" the warnings with '-dontwarn' options."); if (configuration.skipNonPublicLibraryClasses) { logger.warn(" You may also have to remove the option '-skipnonpubliclibraryclasses'."); } logger.warn(" (https://www.guardsquare.com/proguard/manual/troubleshooting#unresolvedclass)"); } int dependencyWarningCount = dependencyWarningPrinter.getWarningCount(); if (dependencyWarningCount > 0) { logger.warn("Warning: there were {} instances of library classes depending on program classes.", dependencyWarningCount); logger.warn(" You must avoid such dependencies, since the program classes will"); logger.warn(" be processed, while the library classes will remain unchanged."); logger.warn(" (https://www.guardsquare.com/proguard/manual/troubleshooting#dependency)"); } int programMemberReferenceWarningCount = programMemberReferenceWarningPrinter.getWarningCount(); if (programMemberReferenceWarningCount > 0) { logger.warn("Warning: there were {} unresolved references to program class members.", programMemberReferenceWarningCount); logger.warn(" Your input classes appear to be inconsistent."); logger.warn(" You may need to recompile the code."); logger.warn(" (https://www.guardsquare.com/proguard/manual/troubleshooting#unresolvedprogramclassmember)"); } int libraryMemberReferenceWarningCount = libraryMemberReferenceWarningPrinter.getWarningCount(); if (libraryMemberReferenceWarningCount > 0) { logger.warn("Warning: there were {} unresolved references to library class members.", libraryMemberReferenceWarningCount); logger.warn(" You probably need to update the library versions."); if (!configuration.skipNonPublicLibraryClassMembers) { logger.warn(" Alternatively, you may have to specify the option "); logger.warn(" '-dontskipnonpubliclibraryclassmembers'."); } if (configuration.skipNonPublicLibraryClasses) { logger.warn(" You may also have to remove the option '-skipnonpubliclibraryclasses'."); } logger.warn(" (https://www.guardsquare.com/proguard/manual/troubleshooting#unresolvedlibraryclassmember)"); } boolean incompatibleOptimization = configuration.optimize && !configuration.shrink; if (incompatibleOptimization) { logger.warn("Warning: optimization is enabled while shrinking is disabled."); logger.warn(" You need to remove the option -dontshrink or optimization might result in classes that fail verification at runtime."); } if ((classReferenceWarningCount > 0 || dependencyWarningCount > 0 || programMemberReferenceWarningCount > 0 || libraryMemberReferenceWarningCount > 0) && !configuration.ignoreWarnings) { throw new IOException("Please correct the above warnings first."); } if ((configuration.note == null || !configuration.note.isEmpty()) && (configuration.warn != null && configuration.warn.isEmpty() || configuration.ignoreWarnings)) { logger.info("Note: you're ignoring all warnings!"); } logger.info("Ignoring unused library classes..."); logger.info(" Original number of library classes: {}", originalLibraryClassPoolSize); logger.info(" Final number of library classes: {}", appView.libraryClassPool.size()); if (configuration.keepKotlinMetadata) { ClassCounter counter = new ClassCounter(); appView.libraryClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( (clazz, kotlinMetadata) -> clazz.accept(counter) ) ); logger.info(" Number of library classes with @kotlin.Metadata: {}", counter.getCount()); } } /** * Extracts a list of exceptions of classes for which not to print notes, * from the keep configuration. */ private StringMatcher createClassNoteExceptionMatcher(List noteExceptions, boolean markClasses) { if (noteExceptions != null) { List noteExceptionNames = new ArrayList(noteExceptions.size()); for (int index = 0; index < noteExceptions.size(); index++) { KeepClassSpecification keepClassSpecification = (KeepClassSpecification)noteExceptions.get(index); if (keepClassSpecification.markClasses || !markClasses) { // If the class itself is being kept, it's ok. String className = keepClassSpecification.className; if (className != null && !containsWildCardReferences(className)) { noteExceptionNames.add(className); } // If all of its extensions are being kept, it's ok too. String extendsClassName = keepClassSpecification.extendsClassName; if (extendsClassName != null && !containsWildCardReferences(extendsClassName)) { noteExceptionNames.add(extendsClassName); } } } if (noteExceptionNames.size() > 0) { return new ListParser(new ClassNameParser()).parse(noteExceptionNames); } } return null; } /** * Extracts a list of exceptions of field or method names for which not to * print notes, from the keep configuration. */ private StringMatcher createClassMemberNoteExceptionMatcher(List noteExceptions, boolean isField) { if (noteExceptions != null) { List noteExceptionNames = new ArrayList(); for (int index = 0; index < noteExceptions.size(); index++) { KeepClassSpecification keepClassSpecification = (KeepClassSpecification)noteExceptions.get(index); List memberSpecifications = isField ? keepClassSpecification.fieldSpecifications : keepClassSpecification.methodSpecifications; if (memberSpecifications != null) { for (int index2 = 0; index2 < memberSpecifications.size(); index2++) { MemberSpecification memberSpecification = (MemberSpecification)memberSpecifications.get(index2); String memberName = memberSpecification.name; if (memberName != null && !containsWildCardReferences(memberName)) { noteExceptionNames.add(memberName); } } } } if (noteExceptionNames.size() > 0) { return new ListParser(new NameParser()).parse(noteExceptionNames); } } return null; } /** * Returns whether the given string contains a numeric reference to a * wild card (""). */ private static boolean containsWildCardReferences(String string) { int openIndex = string.indexOf('<'); if (openIndex < 0) { return false; } int closeIndex = string.indexOf('>', openIndex + 1); if (closeIndex < 0) { return false; } try { Integer.parseInt(string.substring(openIndex + 1, closeIndex)); } catch (NumberFormatException e) { return false; } return true; } } ================================================ FILE: base/src/main/java/proguard/InputReader.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.io.*; import proguard.pass.Pass; import proguard.resources.file.*; import proguard.resources.file.io.ResourceFileDataEntryReader; import proguard.resources.file.visitor.*; import proguard.resources.kotlinmodule.io.KotlinModuleDataEntryReader; import proguard.util.*; import java.io.*; import java.util.List; import static proguard.DataEntryReaderFactory.getFilterExcludingVersionedClasses; /** * This pass reads the input class files. * * @author Eric Lafortune */ public class InputReader implements Pass { private static final Logger logger = LogManager.getLogger(InputReader.class); private static final boolean DONT_READ_LIBRARY_KOTLIN_METADATA = System.getProperty("proguard.dontreadlibrarykotlinmetadata") != null; private final Configuration configuration; // Field that acts as a parameter to the visitors that attach // feature names to classes and resource files. private String featureName; /** * Creates a new InputReader to read input class files as specified by the * given configuration. */ public InputReader(Configuration configuration) { this.configuration = configuration; } /** * Fills the program class pool, library class pool, and resource file * pool by reading files based on the current configuration. */ @Override public void execute(AppView appView) throws IOException { logger.info("Reading input..."); WarningPrinter notePrinter = new WarningLogger(logger, configuration.note); WarningPrinter warningPrinter = new WarningLogger(logger, configuration.warn); DuplicateClassPrinter duplicateClassPrinter = new DuplicateClassPrinter(notePrinter); DuplicateResourceFilePrinter duplicateResourceFilePrinter = new DuplicateResourceFilePrinter(notePrinter); ClassVisitor classPoolFiller = new ClassPresenceFilter(appView.programClassPool, duplicateClassPrinter, new MultiClassVisitor( new ClassPoolFiller(appView.programClassPool), // Attach the current resource name, if any, to any program classes that it visits. new ProgramClassFilter(clazz -> clazz.setFeatureName(featureName)))); // Create a reader to fill the program class pool (while checking for // duplicates). DataEntryReader classReader = new ClassReader(false, configuration.skipNonPublicLibraryClasses, configuration.skipNonPublicLibraryClassMembers, configuration.shrink || configuration.optimize || configuration.obfuscate, configuration.keepKotlinMetadata, warningPrinter, classPoolFiller); // Create a visitor that initializes the references from resource files // to Java classes. DataEntryNameFilter adaptedDataEntryFilter = configuration.adaptResourceFileContents != null ? new DataEntryNameFilter( new ListParser( new FileNameParser()).parse(configuration.adaptResourceFileContents)) : null; // Create a visitor and a reader to fill the resource file pool with // plain resource file instances (while checking for duplicates). ResourceFileVisitor resourceFilePoolFiller = new ResourceFilePresenceFilter(appView.resourceFilePool, duplicateResourceFilePrinter, new MultiResourceFileVisitor( new ResourceFilePoolFiller(appView.resourceFilePool), new MyResourceFileFeatureNameSetter())); DataEntryReader resourceReader = new ResourceFileDataEntryReader(resourceFilePoolFiller, adaptedDataEntryFilter); if (configuration.keepKotlinMetadata) { resourceReader = new NameFilteredDataEntryReader(KotlinConstants.MODULE.FILE_EXPRESSION, new KotlinModuleDataEntryReader(resourceFilePoolFiller), resourceReader); } // Read the program class files and resource files and put them in the // program class pool and resource file pool. readInput("Reading program ", configuration.programJars, new ClassFilter(classReader, resourceReader)); // Check if we have at least some input classes. if (appView.programClassPool.size() == 0) { throw new IOException("The input doesn't contain any classes. Did you specify the proper '-injars' options?"); } // Read the library class files, if any. if (configuration.libraryJars != null && (configuration.printSeeds != null || configuration.shrink || configuration.optimize || configuration.obfuscate || configuration.preverify || configuration.backport)) { // Read the library class files and put then in the library class // pool. readInput("Reading library ", configuration.libraryJars, new ClassFilter( new ClassReader(true, configuration.skipNonPublicLibraryClasses, configuration.skipNonPublicLibraryClassMembers, true, !DONT_READ_LIBRARY_KOTLIN_METADATA && configuration.keepKotlinMetadata, warningPrinter, new ClassPresenceFilter(appView.programClassPool, duplicateClassPrinter, new ClassPresenceFilter(appView.libraryClassPool, duplicateClassPrinter, new ClassPoolFiller(appView.libraryClassPool)))))); } // Print out a summary of the notes, if necessary. int noteCount = notePrinter.getWarningCount(); if (noteCount > 0) { logger.warn("Note: there were {} duplicate class definitions.", noteCount); logger.warn(" (https://www.guardsquare.com/proguard/manual/troubleshooting#duplicateclass)"); } // Print out a summary of the warnings, if necessary. int warningCount = warningPrinter.getWarningCount(); if (warningCount > 0) { logger.warn("Warning: there were {} classes in incorrectly named files.", warningCount); logger.warn(" You should make sure all file names correspond to their class names."); logger.warn(" The directory hierarchies must correspond to the package hierarchies."); logger.warn(" (https://www.guardsquare.com/proguard/manual/troubleshooting#unexpectedclass)"); if (!configuration.ignoreWarnings) { logger.warn(" If you don't mind the mentioned classes not being written out,"); logger.warn(" you could try your luck using the '-ignorewarnings' option."); throw new IOException("Please correct the above warnings first."); } } } /** * Reads all input entries from the given class path. */ private void readInput(String messagePrefix, ClassPath classPath, DataEntryReader reader) throws IOException { readInput(messagePrefix, classPath, 0, classPath.size(), reader); } /** * Reads all input entries from the given section of the given class path. */ public void readInput(String messagePrefix, ClassPath classPath, int fromIndex, int toIndex, DataEntryReader reader) throws IOException { for (int index = fromIndex; index < toIndex; index++) { ClassPathEntry entry = classPath.get(index); if (!entry.isOutput()) { readInput(messagePrefix, entry, reader); } } } /** * Reads the given input class path entry. */ private void readInput(String messagePrefix, ClassPathEntry classPathEntry, DataEntryReader dataEntryReader) throws IOException { try { List filter = getFilterExcludingVersionedClasses(classPathEntry); logger.info("{}{} [{}]{}", messagePrefix, classPathEntry.isDex() ? "dex" : classPathEntry.isApk() ? "apk" : classPathEntry.isAab() ? "aab" : classPathEntry.isJar() ? "jar" : classPathEntry.isAar() ? "aar" : classPathEntry.isWar() ? "war" : classPathEntry.isEar() ? "ear" : classPathEntry.isJmod() ? "jmod" : classPathEntry.isZip() ? "zip" : "directory", classPathEntry.getName(), filter != null || classPathEntry.isFiltered() ? " (filtered)" : "" ); // Create a reader that can unwrap jars, wars, ears, jmods and zips. DataEntryReader reader = new DataEntryReaderFactory(configuration.android) .createDataEntryReader(classPathEntry, dataEntryReader); // Create the data entry source. DataEntrySource source = new DirectorySource(classPathEntry.getFile()); // Set he feature name for the class files and resource files // that we'll read. featureName = classPathEntry.getFeatureName(); // Pump the data entries into the reader. source.pumpDataEntries(reader); } catch (IOException ex) { throw new IOException("Can't read [" + classPathEntry + "] (" + ex.getMessage() + ")", ex); } } /** * This resource file visitor attaches the current resource name, if any, * to any resource files that it visits. */ private class MyResourceFileFeatureNameSetter implements ResourceFileVisitor { // Implementations for ResourceFileVisitor. public void visitAnyResourceFile(ResourceFile resourceFile) { resourceFile.setFeatureName(featureName); } } } ================================================ FILE: base/src/main/java/proguard/KeepClassMemberChecker.java ================================================ /* * 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; import proguard.classfile.util.*; import proguard.classfile.visitor.ClassVisitor; import java.util.List; /** * This class checks if the user has forgotten to specify class members in * some keep options in the configuration. * * @author Eric Lafortune */ public class KeepClassMemberChecker { private final WarningPrinter notePrinter; /** * Creates a new KeepClassMemberChecker. */ public KeepClassMemberChecker(WarningPrinter notePrinter) { this.notePrinter = notePrinter; } /** * Checks if the given class specifications try to keep class members * without actually specifying any, printing notes if necessary. */ public void checkClassSpecifications(List keepClassSpecifications) { if (keepClassSpecifications != null) { for (int index = 0; index < keepClassSpecifications.size(); index++) { KeepClassSpecification keepClassSpecification = (KeepClassSpecification)keepClassSpecifications.get(index); if (!keepClassSpecification.markClasses && (keepClassSpecification.fieldSpecifications == null || keepClassSpecification.fieldSpecifications.size() == 0) && (keepClassSpecification.methodSpecifications == null || keepClassSpecification.methodSpecifications.size() == 0)) { String className = keepClassSpecification.className; if (className == null) { className = keepClassSpecification.extendsClassName; } if (className == null || notePrinter.accepts(className)) { notePrinter.print(className, "Note: the configuration doesn't specify which class members to keep for class '" + (className == null ? ConfigurationConstants.ANY_CLASS_KEYWORD : ClassUtil.externalClassName(className)) + "'"); } } } } } } ================================================ FILE: base/src/main/java/proguard/KeepClassSpecification.java ================================================ /* * 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; /** * This class represents a keep option with class specification. * * @author Eric Lafortune */ public class KeepClassSpecification extends ClassSpecification { public final boolean markClasses; public final boolean markClassMembers; public final boolean markConditionally; public final boolean markDescriptorClasses; public final boolean markCodeAttributes; public final boolean allowShrinking; public final boolean allowOptimization; public final boolean allowObfuscation; public final ClassSpecification condition; /** * Creates a new KeepClassSpecification. * @param markClasses specifies whether to mark the classes. * @param markClassMembers specifies whether to mark the class * members. * @param markConditionally specifies whether to mark the classes and * class members conditionally. If true, * classes and class members are marked, on * the condition that all specified class * members are present. * @param markDescriptorClasses specifies whether to mark the classes in * the descriptors of the marked class members. * @param markCodeAttributes specified whether to mark the code attributes * of the marked class methods. * @param allowShrinking specifies whether shrinking is allowed. * @param allowOptimization specifies whether optimization is allowed. * @param allowObfuscation specifies whether obfuscation is allowed. * @param condition an optional extra condition. * @param classSpecification the specification of classes and class * members. */ public KeepClassSpecification(boolean markClasses, boolean markClassMembers, boolean markConditionally, boolean markDescriptorClasses, boolean markCodeAttributes, boolean allowShrinking, boolean allowOptimization, boolean allowObfuscation, ClassSpecification condition, ClassSpecification classSpecification) { super(classSpecification); this.markClasses = markClasses; this.markClassMembers = markClassMembers; this.markConditionally = markConditionally; this.markDescriptorClasses = markDescriptorClasses; this.markCodeAttributes = markCodeAttributes; this.allowShrinking = allowShrinking; this.allowOptimization = allowOptimization; this.allowObfuscation = allowObfuscation; this.condition = condition; } /** * Creates a new KeepClassSpecification. * * @param markClassesAndMembers specifies whether to mark the classes. * @param markConditionally specifies whether to mark the classes and * class members conditionally. If true, * classes and class members are marked, on * the condition that all specified class * members are present. * @param markDescriptorClasses specifies whether to mark the classes in * the descriptors of the marked class members. * @param markCodeAttributes specified whether to mark the code attributes * of the marked class methods. * @param allowShrinking specifies whether shrinking is allowed. * @param allowOptimization specifies whether optimization is allowed. * @param allowObfuscation specifies whether obfuscation is allowed. * @param condition an optional extra condition. * @param classSpecification the specification of classes and class * members. */ @Deprecated public KeepClassSpecification(boolean markClassesAndMembers, boolean markConditionally, boolean markDescriptorClasses, boolean markCodeAttributes, boolean allowShrinking, boolean allowOptimization, boolean allowObfuscation, ClassSpecification condition, ClassSpecification classSpecification) { this(markClassesAndMembers, markClassesAndMembers, markConditionally, markDescriptorClasses, markCodeAttributes, allowShrinking, allowOptimization, allowObfuscation, condition, classSpecification); } // Implementations for Object. @Override public boolean equals(Object object) { if (object == null || this.getClass() != object.getClass()) { return false; } KeepClassSpecification other = (KeepClassSpecification)object; return this.markClasses == other.markClasses && this.markClassMembers == other.markClassMembers && this.markConditionally == other.markConditionally && this.markDescriptorClasses == other.markDescriptorClasses && this.markCodeAttributes == other.markCodeAttributes && this.allowShrinking == other.allowShrinking && this.allowOptimization == other.allowOptimization && this.allowObfuscation == other.allowObfuscation && (this.condition == null ? other.condition == null : this.condition .equals(other.condition)) && super.equals(other); } @Override public int hashCode() { return (markClasses ? 0 : 1) ^ (markClassMembers ? 0 : 2) ^ (markConditionally ? 0 : 4) ^ (markDescriptorClasses ? 0 : 8) ^ (markCodeAttributes ? 0 : 16) ^ (allowShrinking ? 0 : 32) ^ (allowOptimization ? 0 : 64) ^ (allowObfuscation ? 0 : 128) ^ (condition == null ? 0 : condition.hashCode()) ^ super.hashCode(); } @Override public Object clone() { // try // { return super.clone(); // } // catch (CloneNotSupportedException e) // { // return null; // } } } ================================================ FILE: base/src/main/java/proguard/KeepClassSpecificationVisitorFactory.java ================================================ /* * 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; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.visitor.*; import proguard.classfile.visitor.*; import proguard.util.WildcardManager; import java.util.List; /** * This factory creates visitors to efficiently travel to specified classes and * class members. * * @author Eric Lafortune */ public class KeepClassSpecificationVisitorFactory extends ClassSpecificationVisitorFactory { private final boolean shrinking; private final boolean optimizing; private final boolean obfuscating; /** * Creates a new KeepClassSpecificationVisitorFactory that creates * visitors for the specified goal. * * @param shrinking a flag that specifies whether the visitors are * intended for the shrinking step. * @param optimizing a flag that specifies whether the visitors are * intended for the optimization step. * @param obfuscating a flag that specifies whether the visitors are * intended for the obfuscation step. */ public KeepClassSpecificationVisitorFactory(boolean shrinking, boolean optimizing, boolean obfuscating) { this.shrinking = shrinking; this.optimizing = optimizing; this.obfuscating = obfuscating; } // Overriding implementations for ClassSpecificationVisitorFactory. /** * Constructs a ClassPoolVisitor to efficiently travel to the specified * classes, class members and code attributes. * * @param keepClassSpecifications the specifications of the class(es) and * class members to visit. * @param classVisitor an optional ClassVisitor to be applied to * matching classes. * @param fieldVisitor an optional MemberVisitor to be applied * to matching fields. * @param methodVisitor an optional MemberVisitor to be applied * to matching methods. * @param attributeVisitor an optional AttributeVisitor to be applied * to matching code attributes. */ public ClassPoolVisitor createClassPoolVisitor(List keepClassSpecifications, ClassVisitor classVisitor, MemberVisitor fieldVisitor, MemberVisitor methodVisitor, AttributeVisitor attributeVisitor) { MultiClassPoolVisitor multiClassPoolVisitor = new MultiClassPoolVisitor(); if (keepClassSpecifications != null) { for (int index = 0; index < keepClassSpecifications.size(); index++) { KeepClassSpecification keepClassSpecification = (KeepClassSpecification)keepClassSpecifications.get(index); if ((shrinking && !keepClassSpecification.allowShrinking) || (optimizing && !keepClassSpecification.allowOptimization) || (obfuscating && !keepClassSpecification.allowObfuscation)) { multiClassPoolVisitor.addClassPoolVisitor( createClassPoolVisitor(keepClassSpecification, classVisitor, fieldVisitor, methodVisitor, attributeVisitor)); } } } return multiClassPoolVisitor; } /** * Constructs a ClassPoolVisitor to efficiently travel to the specified * classes, class members, and attributes. * * @param keepClassSpecification the specifications of the class(es) and * class members to visit. * @param classVisitor an optional ClassVisitor to be applied to * matching classes. * @param fieldVisitor an optional MemberVisitor to be applied * to matching fields. * @param methodVisitor an optional MemberVisitor to be applied * to matching methods. * @param attributeVisitor an optional AttributeVisitor to be applied * to matching code attributes. */ public ClassPoolVisitor createClassPoolVisitor(KeepClassSpecification keepClassSpecification, ClassVisitor classVisitor, MemberVisitor fieldVisitor, MemberVisitor methodVisitor, AttributeVisitor attributeVisitor) { // Create a global wildcard manager, so wildcards can be referenced // from regular expressions. They are identified by their indices, // which imposes a number of tricky constraints: // - They need to be parsed in the right order, so the list is filled // out in the expected order (corresponding to the text file // configuration). // - They need to be matched in the right order, so the variable // matchers are matched before they are referenced. WildcardManager wildcardManager = new WildcardManager(); // If specified, let the class visitor also visit the descriptor // classes and the signature classes. if (keepClassSpecification.markDescriptorClasses && classVisitor != null) { fieldVisitor = fieldVisitor == null ? new MemberDescriptorReferencedClassVisitor(classVisitor) : new MultiMemberVisitor( fieldVisitor, new MemberDescriptorReferencedClassVisitor(classVisitor)); methodVisitor = methodVisitor == null ? new MemberDescriptorReferencedClassVisitor(true, classVisitor) : new MultiMemberVisitor( methodVisitor, new MemberDescriptorReferencedClassVisitor(true, classVisitor)); } // Don't visit the classes if not specified. if (!keepClassSpecification.markClasses && !keepClassSpecification.markConditionally) { classVisitor = null; } // Do we have an attribute visitor? if (attributeVisitor != null) { // Don't visit the code attributes if not specified. attributeVisitor = keepClassSpecification.markCodeAttributes ? new AttributeNameFilter(Attribute.CODE, attributeVisitor) : null; } ClassSpecification condition = keepClassSpecification.condition; if (condition != null) { // Parse the condition. We need to parse it before the actual keep // specification, to make sure the list of variable string matchers // is filled out in the right order. // Create a placeholder for the class pool visitor that // corresponds to the actual keep specification. Note that we // visit the entire class pool for each matched class. MultiClassPoolVisitor keepClassPoolVisitor = new MultiClassPoolVisitor(); // Parse the condition. ClassPoolVisitor conditionalKeepClassPoolVisitor = createClassTester(condition, keepClassPoolVisitor, wildcardManager); // Parse the actual keep specification and add it to the // placeholder. keepClassPoolVisitor.addClassPoolVisitor( createClassPoolVisitor(keepClassSpecification, classVisitor, fieldVisitor, methodVisitor, attributeVisitor, wildcardManager)); return conditionalKeepClassPoolVisitor; } else { // Just parse the actual keep specification. return createClassPoolVisitor(keepClassSpecification, classVisitor, fieldVisitor, methodVisitor, attributeVisitor, wildcardManager); } } /** * Constructs a ClassPoolVisitor to efficiently travel to the specified * classes and class members. * * @param keepClassSpecification the specifications of the class(es) and class * members to visit. * @param classVisitor an optional ClassVisitor to be applied to * matching classes. * @param fieldVisitor an optional MemberVisitor to be applied * to matching fields. * @param methodVisitor an optional MemberVisitor to be applied * to matching methods. * @param attributeVisitor an optional AttributeVisitor to be applied * to matching code attributes. * @param wildcardManager a scope for StringMatcher instances * that match wildcards. */ private ClassPoolVisitor createClassPoolVisitor(KeepClassSpecification keepClassSpecification, ClassVisitor classVisitor, MemberVisitor fieldVisitor, MemberVisitor methodVisitor, AttributeVisitor attributeVisitor, WildcardManager wildcardManager) { // If specified, let the marker visit the class and its class // members conditionally. if (keepClassSpecification.markConditionally) { // Parse the condition. We need to parse it before the actual keep // specification, to make sure the list of variable string matchers // is filled out in the right order. // Create a placeholder for the class visitor that corresponds to // the actual keep specification. MultiClassVisitor keepClassVisitor = new MultiClassVisitor(); // Parse the condition. Only add string matchers locally. ClassPoolVisitor conditionalKeepClassPoolVisitor = createClassTester(keepClassSpecification, keepClassVisitor, new WildcardManager(wildcardManager)); // Parse the actual keep specification and add it to the // placeholder. keepClassVisitor.addClassVisitor( createCombinedClassVisitor(keepClassSpecification.attributeNames, keepClassSpecification.fieldSpecifications, keepClassSpecification.methodSpecifications, classVisitor, fieldVisitor, methodVisitor, attributeVisitor, wildcardManager)); return conditionalKeepClassPoolVisitor; } else { // Just parse the actual keep specification. return super.createClassPoolVisitor(keepClassSpecification, classVisitor, fieldVisitor, methodVisitor, attributeVisitor, wildcardManager); } } } ================================================ FILE: base/src/main/java/proguard/KotlinMetadataAdapter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.io.kotlin.KotlinMetadataWriter; import proguard.classfile.kotlin.KotlinMetadataVersion; import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor; import proguard.classfile.visitor.ClassCounter; import proguard.pass.Pass; public class KotlinMetadataAdapter implements Pass { private static final Logger logger = LogManager.getLogger(KotlinMetadataAdapter.class); @Override public void execute(AppView appView) { logger.info("Adapting Kotlin metadata..."); ClassCounter counter = new ClassCounter(); appView.programClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( new KotlinMetadataWriter( (clazz, message) -> logger.warn(clazz.getName(), message), counter ))); logger.info(" Number of Kotlin classes adapted: " + counter.getCount()); } } ================================================ FILE: base/src/main/java/proguard/LibraryKeepChecker.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import java.util.List; /** * This class checks whether some keep rules only keep library classes, no * program classes. That is strange, because library classes never need to * be kept explicitly. * * @author Eric Lafortune */ public class LibraryKeepChecker implements ClassVisitor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final WarningPrinter notePrinter; // Some fields acting as parameters for the class visitor. private String keepName; /** * Creates a new DescriptorKeepChecker. */ public LibraryKeepChecker(ClassPool programClassPool, ClassPool libraryClassPool, WarningPrinter notePrinter) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.notePrinter = notePrinter; } /** * Checks the classes mentioned in the given keep specifications, printing * notes if necessary. */ public void checkClassSpecifications(List keepSpecifications) { if (keepSpecifications != null) { // Go over all individual keep specifications. for (int index = 0; index < keepSpecifications.size(); index++) { KeepClassSpecification keepClassSpecification = (KeepClassSpecification)keepSpecifications.get(index); // Is the keep specification more specific than a general // wildcard? keepName = keepClassSpecification.className; if (keepName != null) { KeepClassSpecificationVisitorFactory visitorFactory = new KeepClassSpecificationVisitorFactory(true, true, true); // Doesn't the specification match any program classes? ClassCounter programClassCounter = new ClassCounter(); programClassPool.accept( visitorFactory .createClassPoolVisitor(keepClassSpecification, programClassCounter, null, null, null)); if (programClassCounter.getCount() == 0) { // Print out notes about any matched library classes. libraryClassPool.accept( visitorFactory .createClassPoolVisitor(keepClassSpecification, this, null, null, null)); } } } } } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) {} @Override public void visitLibraryClass(LibraryClass libraryClass) { String className = libraryClass.getName(); notePrinter.print(className, "Note: the configuration explicitly specifies '" + ClassUtil.externalClassName(keepName) + "' to keep library class '" + ClassUtil.externalClassName(className) + "'"); } } ================================================ FILE: base/src/main/java/proguard/LineWordReader.java ================================================ /* * 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; import java.io.*; import java.net.URL; /** * A WordReader that returns words from a line number reader. * * @author Eric Lafortune */ public class LineWordReader extends WordReader { private final LineNumberReader reader; private final String description; /** * Creates a new LineWordReader for the given input. */ public LineWordReader(LineNumberReader lineNumberReader, String description, File baseDir) throws IOException { super(baseDir); this.reader = lineNumberReader; this.description = description; } /** * Creates a new LineWordReader for the given input. */ public LineWordReader(LineNumberReader lineNumberReader, String description, URL baseURL) throws IOException { super(baseURL); this.reader = lineNumberReader; this.description = description; } // Implementations for WordReader. protected String nextLine() throws IOException { return reader.readLine(); } protected String lineLocationDescription() { return "line " + reader.getLineNumber() + " of " + description; } public void close() throws IOException { super.close(); if (reader != null) { reader.close(); } } } ================================================ FILE: base/src/main/java/proguard/MemberSpecification.java ================================================ /* * 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; import java.util.List; /** * This class stores a specification of class members. The specification is * template-based: the class member names and descriptors can contain wildcards. * * @author Eric Lafortune */ public class MemberSpecification { public int requiredSetAccessFlags; public int requiredUnsetAccessFlags; public final String annotationType; public final String name; public final String descriptor; public final List attributeNames = null; /** * Creates a new option to keep all possible class members. */ public MemberSpecification() { this(0, 0, null, null, null); } /** * Creates a new option to keep the specified class member(s). * * @param requiredSetAccessFlags the class access flags that must be set * in order for the class to apply. * @param requiredUnsetAccessFlags the class access flags that must be unset * in order for the class to apply. * @param annotationType the name of the class that must be an * annotation in order for the class member * to apply. The name may be null to specify * that no annotation is required. * @param name the class member name. The name may be * null to specify any class member or it * may contain "*" or "?" wildcards. * @param descriptor the class member descriptor. The * descriptor may be null to specify any * class member or it may contain * "**", "*", or "?" wildcards. */ public MemberSpecification(int requiredSetAccessFlags, int requiredUnsetAccessFlags, String annotationType, String name, String descriptor) { this.requiredSetAccessFlags = requiredSetAccessFlags; this.requiredUnsetAccessFlags = requiredUnsetAccessFlags; this.annotationType = annotationType; this.name = name; this.descriptor = descriptor; } // Implementations for Object. public boolean equals(Object object) { if (object == null || this.getClass() != object.getClass()) { return false; } MemberSpecification other = (MemberSpecification)object; return (this.requiredSetAccessFlags == other.requiredSetAccessFlags ) && (this.requiredUnsetAccessFlags == other.requiredUnsetAccessFlags ) && (this.annotationType == null ? other.annotationType == null : this.annotationType.equals(other.annotationType)) && (this.name == null ? other.name == null : this.name.equals(other.name) ) && (this.descriptor == null ? other.descriptor == null : this.descriptor.equals(other.descriptor) ); } public int hashCode() { return (requiredSetAccessFlags ) ^ (requiredUnsetAccessFlags ) ^ (annotationType == null ? 0 : annotationType.hashCode()) ^ (name == null ? 0 : name.hashCode() ) ^ (descriptor == null ? 0 : descriptor.hashCode() ); } } ================================================ FILE: base/src/main/java/proguard/MemberValueSpecification.java ================================================ /* * 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; import proguard.util.ArrayUtil; /** * This member specification assigns a constant value or value range to the * class members. * * @author Eric Lafortune */ public class MemberValueSpecification extends MemberSpecification { public Number[] values; /** * Creates a new option to keep all possible class members. */ public MemberValueSpecification() { this(0, 0, null, null, null, null); } /** * Creates a new option to keep the specified class member(s). * * @param requiredSetAccessFlags the class access flags that must be set * in order for the class to apply. * @param requiredUnsetAccessFlags the class access flags that must be unset * in order for the class to apply. * @param annotationType the name of the class that must be an * annotation in order for the class member * to apply. The name may be null to specify * that no annotation is required. * @param name the class member name. The name may be * null to specify any class member or it * may contain "*" or "?" wildcards. * @param descriptor the class member descriptor. The * descriptor may be null to specify any * class member or it may contain * "**", "*", or "?" wildcards. * @param values the constant value or value range * assigned to this class member. */ public MemberValueSpecification(int requiredSetAccessFlags, int requiredUnsetAccessFlags, String annotationType, String name, String descriptor, Number[] values) { super(requiredSetAccessFlags, requiredUnsetAccessFlags, annotationType, name, descriptor); this.values = values; } // Implementations for Object. public boolean equals(Object object) { if (object == null) { return false; } MemberValueSpecification other = (MemberValueSpecification)object; return super.equals(other) && ArrayUtil.equalOrNull(values, other.values); } public int hashCode() { return super.hashCode() ^ ArrayUtil.hashCodeOrNull(values); } } ================================================ FILE: base/src/main/java/proguard/OutputWriter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.ClassPool; import proguard.classfile.io.visitor.ProcessingFlagDataEntryFilter; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.util.ClassUtil; import proguard.configuration.ConfigurationLogger; import proguard.configuration.InitialStateInfo; import proguard.io.*; import proguard.pass.Pass; import proguard.resources.file.ResourceFilePool; import proguard.resources.file.util.ResourceFilePoolNameFunction; import proguard.resources.kotlinmodule.io.KotlinModuleDataEntryWriter; import proguard.util.*; import java.io.*; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.cert.X509Certificate; import java.util.*; /** * This pass writes the output class files and resource files, packaged in * jar files, etc, if required. * * @author Eric Lafortune */ public class OutputWriter implements Pass { private static final Logger logger = LogManager.getLogger(OutputWriter.class); private final Configuration configuration; public OutputWriter(Configuration configuration) { this.configuration = configuration; } /** * Writes the given class pool to class files, based on the current * configuration. */ @Override public void execute(AppView appView) throws IOException { logger.info("Writing output..."); if (configuration.addConfigurationDebugging) { logger.error("Warning: -addconfigurationdebugging is enabled; the resulting build will contain obfuscation information."); logger.error("It should only be used for debugging purposes."); } ClassPath programJars = configuration.programJars; // Construct a filter for files that shouldn't be compressed. StringMatcher uncompressedFilter = configuration.dontCompress == null ? null : new ListParser(new FileNameParser()).parse(configuration.dontCompress); // Get the private key from the key store. KeyStore.PrivateKeyEntry[] privateKeyEntries = retrievePrivateKeys(configuration); // Convert the current time into DOS date and time. Date currentDate = new Date(); int modificationTime = (currentDate.getYear() - 80) << 25 | (currentDate.getMonth() + 1 ) << 21 | currentDate.getDate() << 16 | currentDate.getHours() << 11 | currentDate.getMinutes() << 5 | currentDate.getSeconds() >> 1; // Create a main data entry writer factory for all nested archives. DataEntryWriterFactory dataEntryWriterFactory = new DataEntryWriterFactory(appView.programClassPool, appView.resourceFilePool, modificationTime, uncompressedFilter, configuration.zipAlign, configuration.android, //resourceInfo.pageAlignNativeLibs, configuration.obfuscate, privateKeyEntries ); DataEntryWriter extraDataEntryWriter = null; if (configuration.extraJar != null) { // Extra data entries can optionally be written to a separate jar file. // This prevents duplicates if there are multiple -outjars that are later // combined together, after ProGuard processing. ClassPath extraClassPath = new ClassPath(); extraClassPath.add(new ClassPathEntry(configuration.extraJar, true)); log(extraClassPath, 0, 1, privateKeyEntries); extraDataEntryWriter = new UniqueDataEntryWriter( dataEntryWriterFactory.createDataEntryWriter(extraClassPath, 0, 1, null)); } int firstInputIndex = 0; int lastInputIndex = 0; // Go over all program class path entries. for (int index = 0; index < programJars.size(); index++) { // Is it an input entry? ClassPathEntry entry = programJars.get(index); if (!entry.isOutput()) { // It's an input entry. Remember the highest index. lastInputIndex = index; } else { // It's an output entry. Is it the last one in a // series of output entries? int nextIndex = index + 1; if (nextIndex == programJars.size() || !programJars.get(nextIndex).isOutput()) { log(programJars, lastInputIndex + 1, nextIndex, privateKeyEntries); // Write the processed input entries to the output entries. writeOutput(dataEntryWriterFactory, configuration, appView.programClassPool, appView.initialStateInfo, appView.resourceFilePool, extraDataEntryWriter != null ? // The extraDataEntryWriter must be remain open // until all outputs have been written. new NonClosingDataEntryWriter(extraDataEntryWriter) : // no extraDataEntryWriter supplied null, appView.extraDataEntryNameMap, programJars, firstInputIndex, lastInputIndex + 1, nextIndex); // Start with the next series of input entries. firstInputIndex = nextIndex; } } } if (extraDataEntryWriter != null) { extraDataEntryWriter.close(); } } /** * Gets the private keys from the key stores, based on the given configuration. */ private KeyStore.PrivateKeyEntry[] retrievePrivateKeys(Configuration configuration) throws IOException { // Check the signing variables. List keyStoreFiles = configuration.keyStores; List keyStorePasswords = configuration.keyStorePasswords; List keyAliases = configuration.keyAliases; List keyPasswords = configuration.keyPasswords; // Don't sign if not all of the signing parameters have been // specified. if (keyStoreFiles == null || keyStorePasswords == null || keyAliases == null || keyPasswords == null) { return null; } try { // We'll interpret the configuration in a flexible way, // e.g. with a single key store and multiple keys, or vice versa. int keyCount = Math.max(keyStoreFiles.size(), keyAliases.size()); KeyStore.PrivateKeyEntry[] privateKeys = new KeyStore.PrivateKeyEntry[keyCount]; Map certificates = new HashMap<>(keyCount); for (int index = 0; index < keyCount; index++) { // Create the private key File keyStoreFile = keyStoreFiles .get(Math.min(index, keyStoreFiles .size()-1)); String keyStorePassword = keyStorePasswords.get(Math.min(index, keyStorePasswords.size()-1)); String keyAlias = keyAliases .get(Math.min(index, keyAliases .size()-1)); String keyPassword = keyPasswords .get(Math.min(index, keyPasswords .size()-1)); KeyStore.PrivateKeyEntry privateKeyEntry = retrievePrivateKey(keyStoreFile, keyStorePassword, keyAlias, keyPassword); // Check if the certificate accidentally is a duplicate, // to avoid basic configuration errors. X509Certificate certificate = (X509Certificate)privateKeyEntry.getCertificate(); Integer duplicateIndex = certificates.put(certificate, index); if (duplicateIndex != null) { throw new IllegalArgumentException("Duplicate specified signing certificates #" + (duplicateIndex + 1) + " and #" + (index + 1) + " out of " + keyCount + " [" + certificate.getSubjectDN().getName() + "]"); } // Add the private key to the list. privateKeys[index] = privateKeyEntry; } return privateKeys; } catch (Exception e) { throw new IOException("Can't sign jar ("+e.getMessage()+")", e); } } private KeyStore.PrivateKeyEntry retrievePrivateKey(File keyStoreFile, String keyStorePassword, String keyAlias, String keyPassword) throws IOException, GeneralSecurityException { // Get the private key from the key store. FileInputStream keyStoreInputStream = new FileInputStream(keyStoreFile); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(keyStoreInputStream, keyStorePassword.toCharArray()); keyStoreInputStream.close(); KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(keyPassword.toCharArray()); KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(keyAlias, protectionParameter); if (entry == null) { throw new GeneralSecurityException("Can't find key alias '"+keyAlias+"' in key store ["+keyStoreFile.getPath()+"]"); } return entry; } /** * Transfers the specified input jars to the specified output jars. */ private void writeOutput(DataEntryWriterFactory dataEntryWriterFactory, Configuration configuration, ClassPool programClassPool, InitialStateInfo initialStateInfo, ResourceFilePool resourceFilePool, DataEntryWriter extraDataEntryWriter, ExtraDataEntryNameMap extraDataEntryNameMap, ClassPath classPath, int fromInputIndex, int fromOutputIndex, int toOutputIndex) throws IOException { // Debugging tip: your can wrap data entry writers and readers with // new DebugDataEntryWriter("...", ....) // new DebugDataEntryReader("...", ....) try { // Construct the writer that can write apks, jars, wars, ears, zips, // and directories, cascading over the specified output entries. DataEntryWriter writer = dataEntryWriterFactory.createDataEntryWriter(classPath, fromOutputIndex, toOutputIndex, null); DataEntryWriter resourceWriter = writer; // Adapt plain resource file names that correspond to class names, // if necessary. if (configuration.obfuscate && configuration.adaptResourceFileNames != null) { // Rename processed general resources. resourceWriter = renameResourceFiles(resourceFilePool, resourceWriter); } if (configuration.keepKotlinMetadata && (configuration.shrink || configuration.obfuscate)) { resourceWriter = new NameFilteredDataEntryWriter(KotlinConstants.MODULE.FILE_EXPRESSION, new FilteredDataEntryWriter( new ProcessingFlagDataEntryFilter(resourceFilePool, 0, ProcessingFlags.DONT_PROCESS_KOTLIN_MODULE), new KotlinModuleDataEntryWriter(resourceFilePool, resourceWriter)), resourceWriter); } // By default, just copy resource files into the above writers. DataEntryReader resourceCopier = new DataEntryCopier(resourceWriter); // We're now switching to the reader side, operating on the // contents possibly parsed from the input streams. DataEntryReader resourceRewriter = resourceCopier; // Adapt resource file contents, if allowed. if ((configuration.shrink || configuration.optimize || configuration.obfuscate) && configuration.adaptResourceFileContents != null) { DataEntryReader adaptingContentWriter = resourceRewriter; // Adapt the contents of general resource files (manifests // and native libraries). if (configuration.obfuscate) { adaptingContentWriter = adaptResourceFiles(configuration, programClassPool, resourceWriter); } // Add the overall filter for adapting resource file contents. resourceRewriter = new NameFilteredDataEntryReader(configuration.adaptResourceFileContents, adaptingContentWriter, resourceRewriter); } // Write any kept directories. DataEntryReader reader = writeDirectories(configuration, programClassPool, resourceCopier, resourceRewriter); // Write extra configuration files. reader = writeExtraConfigurationFiles(configuration, programClassPool, initialStateInfo, extraDataEntryNameMap, reader, extraDataEntryWriter != null ? extraDataEntryWriter : writer); // Write classes. DataEntryReader classReader = new ClassFilter(new IdleRewriter(writer), reader); // Write classes attached as extra data entries. DataEntryReader extraClassReader = extraDataEntryWriter != null ? new ClassFilter(new IdleRewriter(extraDataEntryWriter), reader) : classReader; // Write any attached data entries. reader = new ExtraDataEntryReader(extraDataEntryNameMap, classReader, extraClassReader); // Go over the specified input entries and write their processed // versions. new InputReader(configuration).readInput(" Copying resources from program ", classPath, fromInputIndex, fromOutputIndex, reader); // Close all output entries. writer.close(); } catch (IOException ex) { String message = "Can't write [" + classPath.get(fromOutputIndex).getName() + "] (" + ex.getMessage() + ")"; throw new IOException(message, ex); } } /** * Returns a resource writer that writes all extra configuration files to the given extra writer, * and delegates all other resources to the given resource writer. */ private DataEntryReader writeExtraConfigurationFiles(Configuration configuration, ClassPool programClassPool, InitialStateInfo initialStateInfo, ExtraDataEntryNameMap extraDataEntryNameMap, DataEntryReader resourceCopier, DataEntryWriter extraFileWriter) { if (configuration.addConfigurationDebugging) { extraDataEntryNameMap.addExtraDataEntry(ConfigurationLogger.CLASS_MAP_FILENAME); resourceCopier = new NameFilteredDataEntryReader(ConfigurationLogger.CLASS_MAP_FILENAME, new ClassMapDataEntryReplacer(programClassPool, initialStateInfo, extraFileWriter), resourceCopier); } return resourceCopier; } /** * Returns a writer that writes possibly renamed resource files to the * given resource writer. */ private DataEntryWriter renameResourceFiles(ResourceFilePool resourceFilePool, DataEntryWriter dataEntryWriter) { return new FilteredDataEntryWriter(new DataEntryDirectoryFilter(), dataEntryWriter, new RenamedDataEntryWriter(new ResourceFilePoolNameFunction(resourceFilePool), dataEntryWriter)); } /** * Returns a reader that writes all general resource files (manifest, * native libraries, text files) with shrunk, optimized, and obfuscated * contents to the given writer. */ private DataEntryReader adaptResourceFiles(Configuration configuration, ClassPool programClassPool, DataEntryWriter writer) { // Pick a suitable encoding. Charset charset = configuration.android ? StandardCharsets.UTF_8 : Charset.defaultCharset(); // Filter between the various general resource files. return new NameFilteredDataEntryReader("META-INF/MANIFEST.MF,META-INF/*.SF", new ManifestRewriter(programClassPool, charset, writer), new DataEntryRewriter(programClassPool, charset, writer)); } /** * Writes possibly renamed directories that should be preserved to the * given resource copier, and non-directories to the given file copier. */ private DirectoryFilter writeDirectories(Configuration configuration, ClassPool programClassPool, DataEntryReader directoryCopier, DataEntryReader fileCopier) { DataEntryReader directoryRewriter = null; // Wrap the directory copier with a filter and a data entry renamer. if (configuration.keepDirectories != null) { StringFunction packagePrefixFunction = new MapStringFunction(createPackagePrefixMap(programClassPool)); directoryRewriter = new NameFilteredDataEntryReader(configuration.keepDirectories, new RenamedDataEntryReader(packagePrefixFunction, directoryCopier, directoryCopier)); } // Filter on directories and files. return new DirectoryFilter(directoryRewriter, fileCopier); } /** * Creates a map of old package prefixes to new package prefixes, based on * the given class pool. */ private static Map createPackagePrefixMap(ClassPool classPool) { Map packagePrefixMap = new HashMap<>(); Iterator iterator = classPool.classNames(); while (iterator.hasNext()) { String className = iterator.next(); String packagePrefix = ClassUtil.internalPackagePrefix(className); String mappedNewPackagePrefix = packagePrefixMap.get(packagePrefix); if (mappedNewPackagePrefix == null || !mappedNewPackagePrefix.equals(packagePrefix)) { String newClassName = classPool.getClass(className).getName(); String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName); packagePrefixMap.put(packagePrefix, newPackagePrefix); } } return packagePrefixMap; } private static void log(ClassPath classPath, int fromIndex, int toIndex, KeyStore.PrivateKeyEntry[] privateKeyEntries) { for (int index = toIndex - 1; index >= fromIndex; index--) { ClassPathEntry classPathEntry = classPath.get(index); List filter = DataEntryReaderFactory.getFilterExcludingVersionedClasses(classPathEntry); logger.info("Preparing {}output {} [{}]{}", privateKeyEntries == null ? "" : "signed ", classPathEntry.isDex() ? "dex" : classPathEntry.isApk() ? "apk" : classPathEntry.isAab() ? "aab" : classPathEntry.isJar() ? "jar" : classPathEntry.isAar() ? "aar" : classPathEntry.isWar() ? "war" : classPathEntry.isEar() ? "ear" : classPathEntry.isJmod() ? "jmod" : classPathEntry.isZip() ? "zip" : "directory", classPathEntry.getName(), filter != null || classPathEntry.isFiltered() ? " (filtered)" : "" ); } } } ================================================ FILE: base/src/main/java/proguard/ParseException.java ================================================ /* * 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; /** * This Exception signals that a parse exception of some * sort has occurred. * * @author Eric Lafortune */ public class ParseException extends Exception { /** * Constructs a ParseException with null * as its error detail message. */ public ParseException() { super(); } /** * Constructs a ParseException with the specified detail * message. The error message string s can later be * retrieved by the {@link Throwable#getMessage} * method of class Throwable. * * @param s the detail message. */ public ParseException(String s) { super(s); } } ================================================ FILE: base/src/main/java/proguard/ProGuard.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.backport.Backporter; import proguard.classfile.editor.ClassElementSorter; import proguard.classfile.pass.PrimitiveArrayConstantIntroducer; import proguard.classfile.util.PrimitiveArrayConstantReplacer; import proguard.configuration.ConfigurationLoggingAdder; import proguard.configuration.InitialStateInfo; import proguard.evaluation.IncompleteClassHierarchyException; import proguard.logging.Logging; import proguard.mark.Marker; import proguard.obfuscate.NameObfuscationReferenceFixer; import proguard.obfuscate.ObfuscationPreparation; import proguard.obfuscate.Obfuscator; import proguard.obfuscate.ResourceFileNameAdapter; import proguard.optimize.LineNumberTrimmer; import proguard.optimize.Optimizer; import proguard.optimize.gson.GsonOptimizer; import proguard.optimize.peephole.LineNumberLinearizer; import proguard.pass.PassRunner; import proguard.preverify.PreverificationClearer; import proguard.preverify.Preverifier; import proguard.preverify.SubroutineInliner; import proguard.shrink.Shrinker; import proguard.strip.KotlinAnnotationStripper; import proguard.util.ConstantMatcher; import proguard.util.ListParser; import proguard.util.NameParser; import proguard.util.PrintWriterUtil; import proguard.util.StringMatcher; import proguard.util.kotlin.KotlinUnsupportedVersionChecker; import proguard.util.kotlin.asserter.KotlinMetadataVerifier; import java.io.IOException; import java.io.PrintWriter; /** * Tool for shrinking, optimizing, obfuscating, and preverifying Java classes. * * @author Eric Lafortune */ public class ProGuard { private static final Logger logger = LogManager.getLogger(ProGuard.class); public static final String VERSION = "ProGuard, version " + getVersion(); /** * A data object containing pass inputs in a centralized location. Passes can access and update the information * at any point in the pipeline. */ private final AppView appView; private final PassRunner passRunner; private final Configuration configuration; /** * Creates a new ProGuard object to process jars as specified by the given * configuration. */ public ProGuard(Configuration configuration) { this.appView = new AppView(); this.passRunner = new PassRunner(); this.configuration = configuration; } /** * Performs all subsequent ProGuard operations. */ public void execute() throws Exception { Logging.configureVerbosity(configuration.verbose); logger.always().log(VERSION); try { checkGpl(); // Set the -keepkotlinmetadata option if necessary. if (!configuration.dontProcessKotlinMetadata) { configuration.keepKotlinMetadata = requiresKotlinMetadata(); } if (configuration.printConfiguration != null) { printConfiguration(); } checkConfiguration(); if (configuration.programJars.hasOutput()) { checkUpToDate(); } if (configuration.targetClassVersion != 0) { configuration.backport = true; } readInput(); if (configuration.shrink || configuration.optimize || configuration.obfuscate || configuration.preverify) { clearPreverification(); } if (configuration.printSeeds != null || configuration.backport || configuration.shrink || configuration.optimize || configuration.obfuscate || configuration.preverify || configuration.addConfigurationDebugging || configuration.keepKotlinMetadata) { initialize(); mark(); } checkConfigurationAfterInitialization(); if (configuration.addConfigurationDebugging) { // Remember the initial state of the program classpool and resource filepool // before shrinking / obfuscation / optimization. appView.initialStateInfo = new InitialStateInfo(appView.programClassPool); } if (configuration.keepKotlinMetadata) { stripKotlinMetadataAnnotations(); } if (configuration.optimize || configuration.obfuscate) { introducePrimitiveArrayConstants(); } if (configuration.backport) { backport(); } if (configuration.addConfigurationDebugging) { addConfigurationLogging(); } if (configuration.printSeeds != null) { printSeeds(); } if (configuration.preverify || configuration.android) { inlineSubroutines(); } if (configuration.shrink) { shrink(false); } // Create a matcher for filtering optimizations. StringMatcher filter = configuration.optimizations != null ? new ListParser(new NameParser()).parse(configuration.optimizations) : new ConstantMatcher(true); if (configuration.optimize && filter.matches(Optimizer.LIBRARY_GSON)) { optimizeGson(); } if (configuration.optimize) { optimize(); linearizeLineNumbers(); } if (configuration.obfuscate) { obfuscate(); } if (configuration.keepKotlinMetadata) { adaptKotlinMetadata(); } if (configuration.optimize || configuration.obfuscate) { expandPrimitiveArrayConstants(); } if (configuration.targetClassVersion != 0) { target(); } if (configuration.preverify) { preverify(); } // Trim line numbers after preverification as this might // also remove some instructions. if (configuration.optimize || configuration.preverify) { trimLineNumbers(); } if (configuration.shrink || configuration.optimize || configuration.obfuscate || configuration.preverify) { sortClassElements(); } if (configuration.programJars.hasOutput()) { writeOutput(); } if (configuration.dump != null) { dump(); } } catch (UpToDateChecker.UpToDateException ignore) {} catch (IncompleteClassHierarchyException e) { throw new RuntimeException( System.lineSeparator() + System.lineSeparator() + "It appears you are missing some classes resulting in an incomplete class hierarchy, " + System.lineSeparator() + "please refer to the troubleshooting page in the manual: " + System.lineSeparator() + "https://www.guardsquare.com/en/products/proguard/manual/troubleshooting#superclass" + System.lineSeparator() ); } } /** * Checks the GPL. */ private void checkGpl() { GPL.check(); } private boolean requiresKotlinMetadata() { return configuration.keepKotlinMetadata || (configuration.keep != null && configuration.keep.stream().anyMatch( keepClassSpecification -> ! keepClassSpecification.allowObfuscation && ! keepClassSpecification.allowShrinking && "kotlin/Metadata".equals(keepClassSpecification.className) )); } /** * Prints out the configuration that ProGuard is using. */ private void printConfiguration() throws IOException { PrintWriter pw = PrintWriterUtil.createPrintWriterOut(configuration.printConfiguration); try (ConfigurationWriter configurationWriter = new ConfigurationWriter(pw)) { configurationWriter.write(configuration); } } /** * Checks the configuration for conflicts and inconsistencies. */ private void checkConfiguration() throws IOException { new ConfigurationVerifier(configuration).check(); } /** * Checks whether the output is up-to-date. */ private void checkUpToDate() { new UpToDateChecker(configuration).check(); } /** * Reads the input class files. */ private void readInput() throws Exception { // Fill the program class pool and the library class pool. passRunner.run(new InputReader(configuration), appView); } /** * Clears any JSE preverification information from the program classes. */ private void clearPreverification() throws Exception { passRunner.run(new PreverificationClearer(), appView); } /** * Initializes the cross-references between all classes, performs some * basic checks, and shrinks the library class pool. */ private void initialize() throws Exception { if (configuration.keepKotlinMetadata) { passRunner.run(new KotlinUnsupportedVersionChecker(), appView); } passRunner.run(new Initializer(configuration), appView); if (configuration.keepKotlinMetadata && configuration.enableKotlinAsserter) { passRunner.run(new KotlinMetadataVerifier(configuration), appView); } } /** * Marks the classes, class members and attributes to be kept or encrypted, * by setting the appropriate access flags. */ private void mark() throws Exception { passRunner.run(new Marker(configuration), appView); } /** * Strips the Kotlin metadata annotation where possible. */ private void stripKotlinMetadataAnnotations() throws Exception { passRunner.run(new KotlinAnnotationStripper(configuration), appView); } /** * Checks the configuration after it has been initialized. */ private void checkConfigurationAfterInitialization() throws Exception { passRunner.run(new AfterInitConfigurationVerifier(configuration), appView); } /** * Replaces primitive array initialization code by primitive array constants. */ private void introducePrimitiveArrayConstants() throws Exception { passRunner.run(new PrimitiveArrayConstantIntroducer(), appView); } /** * Backports java language features to the specified target version. */ private void backport() throws Exception { passRunner.run(new Backporter(configuration), appView); } /** * Adds configuration logging code, providing suggestions on improving * the ProGuard configuration. */ private void addConfigurationLogging() throws Exception { passRunner.run(new ConfigurationLoggingAdder(), appView); } /** * Prints out classes and class members that are used as seeds in the * shrinking and obfuscation steps. */ private void printSeeds() throws Exception { passRunner.run(new SeedPrinter(configuration), appView); } /** * Performs the subroutine inlining step. */ private void inlineSubroutines() throws Exception { // Perform the actual inlining. passRunner.run(new SubroutineInliner(configuration), appView); } /** * Performs the shrinking step. */ private void shrink(boolean afterOptimizer) throws Exception { // Perform the actual shrinking. passRunner.run(new Shrinker(configuration, afterOptimizer), appView); if (configuration.keepKotlinMetadata && configuration.enableKotlinAsserter) { passRunner.run(new KotlinMetadataVerifier(configuration), appView); } } /** * Optimizes usages of the Gson library. */ private void optimizeGson() throws Exception { // Perform the Gson optimization. passRunner.run(new GsonOptimizer(configuration), appView); } /** * Performs the optimization step. */ private void optimize() throws Exception { Optimizer optimizer = new Optimizer(configuration); for (int optimizationPass = 0; optimizationPass < configuration.optimizationPasses; optimizationPass++) { // Perform the actual optimization. passRunner.run(optimizer, appView); // Shrink again, if we may. if (configuration.shrink) { shrink(true); } } } /** * Disambiguates the line numbers of all program classes, after * optimizations like method inlining and class merging. */ private void linearizeLineNumbers() throws Exception { passRunner.run(new LineNumberLinearizer(), appView); } /** * Performs the obfuscation step. */ private void obfuscate() throws Exception { passRunner.run(new ObfuscationPreparation(configuration), appView); // Perform the actual obfuscation. passRunner.run(new Obfuscator(configuration), appView); // Adapt resource file names that correspond to class names, if necessary. if (configuration.adaptResourceFileNames != null) { passRunner.run(new ResourceFileNameAdapter(configuration), appView); } // Fix the Kotlin modules so the filename matches and the class names match. passRunner.run(new NameObfuscationReferenceFixer(configuration), appView); if (configuration.keepKotlinMetadata && configuration.enableKotlinAsserter) { passRunner.run(new KotlinMetadataVerifier(configuration), appView); } } /** * Adapts Kotlin Metadata annotations. */ private void adaptKotlinMetadata() throws Exception { passRunner.run(new KotlinMetadataAdapter(), appView); } /** * Expands primitive array constants back to traditional primitive array * initialization code. */ private void expandPrimitiveArrayConstants() { appView.programClassPool.classesAccept(new PrimitiveArrayConstantReplacer()); } /** * Sets that target versions of the program classes. */ private void target() throws Exception { passRunner.run(new Targeter(configuration), appView); } /** * Performs the preverification step. */ private void preverify() throws Exception { // Perform the actual preverification. passRunner.run(new Preverifier(configuration), appView); } /** * Trims the line number table attributes of all program classes. */ private void trimLineNumbers() throws Exception { passRunner.run(new LineNumberTrimmer(), appView); } /** * Sorts the elements of all program classes. */ private void sortClassElements() { appView.programClassPool.classesAccept( new ClassElementSorter( /* sortInterfaces = */ true, /* sortConstants = */ true, // Sorting members can cause problems with code such as clazz.getMethods()[1] /* sortMembers = */ false, // PGD-192: Sorting attributes can cause problems for some compilers /* sortAttributes = */ false ) ); } /** * Writes the output class files. */ private void writeOutput() throws Exception { // Write out the program class pool. passRunner.run(new OutputWriter(configuration), appView); } /** * Prints out the contents of the program classes. */ private void dump() throws Exception { passRunner.run(new Dumper(configuration), appView); } /** * Returns the implementation version from the manifest. */ public static String getVersion() { Package pack = ProGuard.class.getPackage(); if (pack != null) { String version = pack.getImplementationVersion(); if (version != null) { return version; } } return "undefined"; } /** * The main method for ProGuard. */ public static void main(String[] args) { if (args.length == 0) { logger.warn(VERSION); logger.warn("Usage: java proguard.ProGuard [options ...]"); System.exit(1); } // Create the default options. Configuration configuration = new Configuration(); try { // Parse the options specified in the command line arguments. try (ConfigurationParser parser = new ConfigurationParser(args, System.getProperties())) { parser.parse(configuration); } // Execute ProGuard with these options. new ProGuard(configuration).execute(); } catch (Exception ex) { logger.error("Unexpected error", ex); System.exit(1); } System.exit(0); } } ================================================ FILE: base/src/main/java/proguard/SeedPrinter.java ================================================ /* * 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.visitor.*; import proguard.optimize.*; import proguard.pass.Pass; import proguard.util.PrintWriterUtil; import java.io.*; /** * This pass prints out the seeds specified by keep options. * * @author Eric Lafortune */ public class SeedPrinter implements Pass { private static final Logger logger = LogManager.getLogger(SeedPrinter.class); private final Configuration configuration; public SeedPrinter(Configuration configuration) { this.configuration = configuration; } /** * Prints out the seeds for the classes in the given program class pool. * * @throws IOException if an IO error occurs while writing the configuration. */ @Override public void execute(AppView appView) throws IOException { logger.info("Printing kept classes, fields, and methods..."); PrintWriter printWriter = PrintWriterUtil.createPrintWriterOut(configuration.printSeeds); try { // Check if we have at least some keep commands. if (configuration.keep == null) { throw new IOException("You have to specify '-keep' options if you want to write out kept elements with '-printseeds'."); } // Clean up any old processing info. appView.programClassPool.classesAccept(new ClassCleaner()); appView.libraryClassPool.classesAccept(new ClassCleaner()); // Create a visitor for printing out the seeds. We're printing out // the program elements that are preserved against shrinking, // optimization, or obfuscation. KeepMarker keepMarker = new KeepMarker(); ClassPoolVisitor classPoolvisitor = new KeepClassSpecificationVisitorFactory(true, true, true) .createClassPoolVisitor(configuration.keep, keepMarker, keepMarker, keepMarker, null); // Mark the seeds. appView.programClassPool.accept(classPoolvisitor); appView.libraryClassPool.accept(classPoolvisitor); // Print out the seeds. SimpleClassPrinter printer = new SimpleClassPrinter(false, printWriter); appView.programClassPool.classesAcceptAlphabetically( new MultiClassVisitor( new KeptClassFilter(printer), new AllMemberVisitor(new KeptMemberFilter(printer)) )); } finally { PrintWriterUtil.closePrintWriter(configuration.printSeeds, printWriter); } } } ================================================ FILE: base/src/main/java/proguard/SubclassedClassFilter.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates all its method calls to another ClassVisitor, * but only for Clazz objects that are being subclassed. * * @author Eric Lafortune */ final class SubclassedClassFilter implements ClassVisitor { private final ClassVisitor classVisitor; public SubclassedClassFilter(ClassVisitor classVisitor) { this.classVisitor = classVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { if (programClass.subClassCount > 0) { classVisitor.visitProgramClass(programClass); } } @Override public void visitLibraryClass(LibraryClass libraryClass) { if (libraryClass.subClassCount > 0) { classVisitor.visitLibraryClass(libraryClass); } } } ================================================ FILE: base/src/main/java/proguard/Targeter.java ================================================ /* * 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.ClassPool; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.ClassVersionSetter; import proguard.pass.Pass; import java.io.IOException; import java.util.*; /** * This pass sets the target version on program classes. * * @author Eric Lafortune */ public class Targeter implements Pass { private static final Logger logger = LogManager.getLogger(Targeter.class); private final Configuration configuration; public Targeter(Configuration configuration) { this.configuration = configuration; } /** * Sets the target version on classes in the given program class pool. */ @Override public void execute(AppView appView) throws IOException { logger.info("Setting target versions..."); Set newerClassVersions = configuration.warn != null ? null : new HashSet(); appView.programClassPool.classesAccept(new ClassVersionSetter(configuration.targetClassVersion, newerClassVersions)); if (newerClassVersions != null && newerClassVersions.size() > 0) { logger.error("Warning: some classes have more recent versions ("); Iterator iterator = newerClassVersions.iterator(); while (iterator.hasNext()) { Integer classVersion = (Integer)iterator.next(); logger.error(ClassUtil.externalClassVersion(classVersion.intValue())); if (iterator.hasNext()) { logger.error(","); } } logger.error(")"); logger.error(" than the target version ({}).", ClassUtil.externalClassVersion(configuration.targetClassVersion)); if (!configuration.ignoreWarnings) { logger.error(" If you are sure this is not a problem,"); logger.error(" you could try your luck using the '-ignorewarnings' option."); throw new IOException("Please correct the above warnings first."); } } } } ================================================ FILE: base/src/main/java/proguard/UpToDateChecker.java ================================================ /* * 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; import java.net.*; /** * This class checks whether the output is up to date. * * @author Eric Lafortune */ public class UpToDateChecker { private static final Logger logger = LogManager.getLogger(UpToDateChecker.class); private final Configuration configuration; /** * Creates a new UpToDateChecker with the given configuration. */ public UpToDateChecker(Configuration configuration) { this.configuration = configuration; } /** * Returns whether the output is up to date, based on the modification times * of the input jars, output jars, and library jars (or directories). */ public void check() throws UpToDateException { try { ModificationTimeChecker checker = new ModificationTimeChecker(); checker.updateInputModificationTime(configuration.lastModified); ClassPath programJars = configuration.programJars; ClassPath libraryJars = configuration.libraryJars; // Check the dates of the program jars, if any. if (programJars != null) { for (int index = 0; index < programJars.size(); index++) { // Update the input and output modification times. ClassPathEntry classPathEntry = programJars.get(index); checker.updateModificationTime(classPathEntry.getFile(), classPathEntry.isOutput()); } } // Check the dates of the library jars, if any. if (libraryJars != null) { for (int index = 0; index < libraryJars.size(); index++) { // Update the input modification time. ClassPathEntry classPathEntry = libraryJars.get(index); checker.updateModificationTime(classPathEntry.getFile(), false); } } // Check the dates of the auxiliary input files. checker.updateInputModificationTime(configuration.applyMapping); checker.updateInputModificationTime(configuration.obfuscationDictionary); checker.updateInputModificationTime(configuration.classObfuscationDictionary); checker.updateInputModificationTime(configuration.packageObfuscationDictionary); // Check the dates of the auxiliary output files. checker.updateOutputModificationTime(configuration.printSeeds); checker.updateOutputModificationTime(configuration.printUsage); checker.updateOutputModificationTime(configuration.printMapping); checker.updateOutputModificationTime(configuration.printConfiguration); checker.updateOutputModificationTime(configuration.dump); } catch (IllegalStateException e) { // The output is outdated. return; } logger.always().log("The output seems up to date"); throw new UpToDateException(); } /** * This class maintains the modification times of input and output. * The methods throw an IllegalStateException if the output appears * outdated. */ private static class ModificationTimeChecker { private long inputModificationTime = Long.MIN_VALUE; private long outputModificationTime = Long.MAX_VALUE; /** * Updates the input modification time based on the given file or * directory (recursively). */ public void updateInputModificationTime(URL url) { if (url != null && url.getProtocol().equals("file")) { try { updateModificationTime(new File(url.toURI()), false); } catch (URISyntaxException ignore) {} } } /** * Updates the input modification time based on the given file or * directory (recursively). */ public void updateInputModificationTime(File file) { if (file != null) { updateModificationTime(file, false); } } /** * Updates the input modification time based on the given file or * directory (recursively). */ public void updateOutputModificationTime(File file) { if (file != null && file.getName().length() > 0) { updateModificationTime(file, true); } } /** * Updates the specified modification time based on the given file or * directory (recursively). */ public void updateModificationTime(File file, boolean isOutput) { // Is it a directory? if (file.isDirectory()) { // Ignore the directory's modification time; just recurse on // its files. File[] files = file.listFiles(); // Still, an empty output directory is probably a sign that it // is not up to date. if (files.length == 0 && isOutput) { updateOutputModificationTime(Long.MIN_VALUE); } for (int index = 0; index < files.length; index++) { updateModificationTime(files[index], isOutput); } } else { // Update with the file's modification time. updateModificationTime(file.lastModified(), isOutput); } } /** * Updates the specified modification time. */ public void updateModificationTime(long time, boolean isOutput) { if (isOutput) { updateOutputModificationTime(time); } else { updateInputModificationTime(time); } } /** * Updates the input modification time. */ public void updateInputModificationTime(long time) { if (inputModificationTime < time) { inputModificationTime = time; checkModificationTimes(); } } /** * Updates the output modification time. */ public void updateOutputModificationTime(long time) { if (outputModificationTime > time) { outputModificationTime = time; checkModificationTimes(); } } private void checkModificationTimes() { if (inputModificationTime > outputModificationTime) { throw new IllegalStateException("The output is outdated"); } } } /** * This Exception is thrown when the output is up-to-date. */ public static class UpToDateException extends RuntimeException {} } ================================================ FILE: base/src/main/java/proguard/WordReader.java ================================================ /* * 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; import java.io.*; import java.net.URL; /** * An abstract reader of words, with the possibility to include other readers. * Words are separated by spaces or broken off at delimiters. Words containing * spaces or delimiters can be quoted with single or double quotes. * Comments (everything starting with '#' on a single line) are ignored. * * @author Eric Lafortune */ public abstract class WordReader { private static final char COMMENT_CHARACTER = '#'; private File baseDir; private URL baseURL; private WordReader includeWordReader; private String currentLine; private int currentLineLength; private int currentIndex; private String currentWord; private String currentComments; /** * Creates a new WordReader with the given base directory. */ protected WordReader(File baseDir) { this.baseDir = baseDir; } /** * Creates a new WordReader with the given base URL. */ protected WordReader(URL baseURL) { this.baseURL = baseURL; } /** * Sets the base directory of this reader. */ public void setBaseDir(File baseDir) { if (includeWordReader != null) { includeWordReader.setBaseDir(baseDir); } else { this.baseDir = baseDir; } } /** * Returns the base directory of this reader, if any. */ public File getBaseDir() { return includeWordReader != null ? includeWordReader.getBaseDir() : baseDir; } /** * Returns the base URL of this reader, if any. */ public URL getBaseURL() { return includeWordReader != null ? includeWordReader.getBaseURL() : baseURL; } /** * Specifies to start reading words from the given WordReader. When it is * exhausted, this WordReader will continue to provide its own words. * * @param newIncludeWordReader the WordReader that will start reading words. */ public void includeWordReader(WordReader newIncludeWordReader) { if (includeWordReader == null) { includeWordReader = newIncludeWordReader; } else { includeWordReader.includeWordReader(newIncludeWordReader); } } /** * Reads a word from this WordReader, or from one of its active included * WordReader objects. * * @param isFileName return a complete line (or argument), if the word * isn't an option (it doesn't start with '-'). * @param expectSingleFile if true, the remaining line is expected to be a * single file name (excluding path separator), * otherwise multiple files might be specified * using the path separator. * @return the read word. */ public String nextWord(boolean isFileName, boolean expectSingleFile) throws IOException { currentWord = null; // See if we have an included reader to produce a word. if (includeWordReader != null) { // Does the included word reader still produce a word? currentWord = includeWordReader.nextWord(isFileName, expectSingleFile); if (currentWord != null) { // Return it if so. return currentWord; } // Otherwise close and ditch the word reader. includeWordReader.close(); includeWordReader = null; } // Get a word from this reader. // Skip any whitespace and comments left on the current line. if (currentLine != null) { // Skip any leading whitespace. while (currentIndex < currentLineLength && Character.isWhitespace(currentLine.charAt(currentIndex))) { currentIndex++; } // Skip any comments. if (currentIndex < currentLineLength && isComment(currentLine.charAt(currentIndex))) { currentIndex = currentLineLength; } } // Make sure we have a non-blank line. while (currentLine == null || currentIndex == currentLineLength) { currentLine = nextLine(); if (currentLine == null) { return null; } currentLineLength = currentLine.length(); // Skip any leading whitespace. currentIndex = 0; while (currentIndex < currentLineLength && Character.isWhitespace(currentLine.charAt(currentIndex))) { currentIndex++; } // Remember any leading comments. if (currentIndex < currentLineLength && isComment(currentLine.charAt(currentIndex))) { // Remember the comments. String comment = currentLine.substring(currentIndex + 1); currentComments = currentComments == null ? comment : currentComments + '\n' + comment; // Skip the comments. currentIndex = currentLineLength; } } // Find the word starting at the current index. int startIndex = currentIndex; int endIndex; char startChar = currentLine.charAt(startIndex); if (isQuote(startChar)) { // The next word is starting with a quote character. // Skip the opening quote. startIndex++; // The next word is a quoted character string. // Find the closing quote. do { currentIndex++; if (currentIndex == currentLineLength) { currentWord = currentLine.substring(startIndex-1, currentIndex); throw new IOException("Missing closing quote for "+locationDescription()); } } while (currentLine.charAt(currentIndex) != startChar); endIndex = currentIndex++; } else if (isFileName && !isOption(startChar)) { // The next word is a (possibly optional) file name. // Find the end of the line, the first path separator, the first // option, or the first comment. while (currentIndex < currentLineLength) { char currentCharacter = currentLine.charAt(currentIndex); if (isFileDelimiter(currentCharacter, !expectSingleFile) || ((isOption(currentCharacter) || isComment(currentCharacter)) && Character.isWhitespace(currentLine.charAt(currentIndex-1)))) { break; } currentIndex++; } endIndex = currentIndex; // Trim any trailing whitespace. while (endIndex > startIndex && Character.isWhitespace(currentLine.charAt(endIndex-1))) { endIndex--; } } else if (isDelimiter(startChar)) { // The next word is a single delimiting character. endIndex = ++currentIndex; } else { // The next word is a simple character string. // Find the end of the line, the first delimiter, or the first // white space. while (currentIndex < currentLineLength) { char currentCharacter = currentLine.charAt(currentIndex); if (isNonStartDelimiter(currentCharacter) || Character.isWhitespace(currentCharacter) || isComment(currentCharacter)) { break; } currentIndex++; } endIndex = currentIndex; } // Remember and return the parsed word. currentWord = currentLine.substring(startIndex, endIndex); return currentWord; } /** * Returns the comments collected before returning the last word. * Starts collecting new comments. * * @return the collected comments, or null if there weren't any. */ public String lastComments() throws IOException { if (includeWordReader == null) { String comments = currentComments; currentComments = null; return comments; } else { return includeWordReader.lastComments(); } } /** * Constructs a readable description of the current position in this * WordReader and its included WordReader objects. * * @return the description. */ public String locationDescription() { return (includeWordReader == null ? (currentWord == null ? "end of " : "'" + currentWord + "' in " ) : (includeWordReader.locationDescription() + ",\n" + " included from ")) + lineLocationDescription(); } /** * Reads a line from this WordReader, or from one of its active included * WordReader objects. * * @return the read line. */ protected abstract String nextLine() throws IOException; /** * Returns a readable description of the current WordReader position. * * @return the description. */ protected abstract String lineLocationDescription(); /** * Closes the FileWordReader. */ public void close() throws IOException { // Close and ditch the included word reader, if any. if (includeWordReader != null) { includeWordReader.close(); includeWordReader = null; } } // Small utility methods. private boolean isOption(char character) { return character == '-'; } private boolean isComment(char character) { return character == COMMENT_CHARACTER; } private boolean isDelimiter(char character) { return isStartDelimiter(character) || isNonStartDelimiter(character); } private boolean isStartDelimiter(char character) { return character == '@'; } private boolean isNonStartDelimiter(char character) { return character == '{' || character == '}' || character == '(' || character == ')' || character == ',' || character == ';' || character == File.pathSeparatorChar; } private boolean isFileDelimiter(char character, boolean includePathSeparator) { return character == '(' || character == ')' || character == ',' || character == ';' || (includePathSeparator && character == File.pathSeparatorChar); } private boolean isQuote(char character) { return character == '\'' || character == '"'; } } ================================================ FILE: base/src/main/java/proguard/backport/AbstractAPIConverter.java ================================================ /* * 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.backport; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.*; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.util.*; /** * Abstract base class for API converter implementations. *

* By default, this class acts as ClassVisitor and will replace any * occurrence of the specified methods / types as configured by the * actual implementation. * * @see StreamSupportConverter * @see JSR310Converter * * @author Thomas Neidhart */ class AbstractAPIConverter implements ClassVisitor, // Implementation interfaces. MemberVisitor, AttributeVisitor, InstructionVisitor, ConstantVisitor, LocalVariableInfoVisitor, LocalVariableTypeInfoVisitor, AnnotationVisitor, ElementValueVisitor { private static final Logger logger = LogManager.getFormatterLogger(AbstractAPIConverter.class); private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final WarningPrinter warningPrinter; private final ClassVisitor modifiedClassVisitor; private final InstructionVisitor extraInstructionVisitor; private TypeReplacement[] typeReplacements; private MethodReplacement[] methodReplacements; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true); private ConstantPoolEditor constantPoolEditor; private int referencingOffset; private Method referencingMethod; private boolean classModified; private boolean instructionReplaced; /** * Create a new AbstractAPIConverter instance. */ AbstractAPIConverter(ClassPool programClassPool, ClassPool libraryClassPool, WarningPrinter warningPrinter, ClassVisitor modifiedClassVisitor, InstructionVisitor extraInstructionVisitor) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.warningPrinter = warningPrinter; this.modifiedClassVisitor = modifiedClassVisitor; this.extraInstructionVisitor = extraInstructionVisitor; } protected MethodReplacement replace(String className, String methodName, String methodDesc, String replacementClassName, String replacementMethodName, String replacementMethodDesc) { MethodReplacement methodReplacement = new MethodReplacement(className, methodName, methodDesc, replacementClassName, replacementMethodName, replacementMethodDesc); return methodReplacement.isValid() ? methodReplacement : missing(className, methodName, methodDesc); } protected TypeReplacement replace(String className, String replacementClassName) { TypeReplacement typeReplacement = new TypeReplacement(className, replacementClassName); return typeReplacement.isValid() ? typeReplacement : missing(className); } protected MethodReplacement missing(String className, String methodName, String methodDesc) { return new MissingMethodReplacement(className, methodName, methodDesc); } protected TypeReplacement missing(String className) { return new MissingTypeReplacement(className); } protected void setTypeReplacements(TypeReplacement[] replacements) { this.typeReplacements = replacements; } protected void setMethodReplacements(MethodReplacement[] replacements) { this.methodReplacements = replacements; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) {} @Override public void visitProgramClass(ProgramClass programClass) { constantPoolEditor = new ConstantPoolEditor(programClass); classModified = false; // We need to update the code attributes first. programClass.methodsAccept( new AllAttributeVisitor( new AttributeNameFilter(Attribute.CODE, this))); // Update the class constants directly. programClass.constantPoolEntriesAccept( new ConstantTagFilter(Constant.CLASS, this)); // Update descriptors and attributes of fields and methods. programClass.fieldsAccept(this); programClass.methodsAccept(this); // Update the class attributes. programClass.attributesAccept(this); if (classModified) { // Remove replaced and now unused constant pool entries. programClass.accept(new ConstantPoolShrinker()); if (modifiedClassVisitor != null) { // Mark this class as being modified. modifiedClassVisitor.visitProgramClass(programClass); } } } // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) {} @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { programField.u2descriptorIndex = updateDescriptor(programClass, programField.u2descriptorIndex); programField.attributesAccept(programClass, this); } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { programMethod.u2descriptorIndex = updateDescriptor(programClass, programMethod.u2descriptorIndex); // Update the remaining attributes, except for the code attribute, // which has already been updated. programMethod.attributesAccept(programClass, new AttributeNameFilter("!" + Attribute.CODE, this)); } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttributeEditor.reset(codeAttribute.u4codeLength); codeAttribute.instructionsAccept(clazz, method, this); if (codeAttributeEditor.isModified()) { codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } // Update the nested attributes. codeAttribute.attributesAccept(clazz, method, this); } @Override public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } @Override public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } @Override public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) { signatureAttribute.u2signatureIndex = updateDescriptor(clazz, signatureAttribute.u2signatureIndex); } @Override public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) { annotationsAttribute.annotationsAccept(clazz, this); } @Override public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) { parameterAnnotationsAttribute.annotationsAccept(clazz, method, this); } @Override public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) { annotationDefaultAttribute.defaultValueAccept(clazz, this); } // Implementations for LocalVariableInfoVisitor. @Override public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) { localVariableInfo.u2descriptorIndex = updateDescriptor(clazz, localVariableInfo.u2descriptorIndex); } // Implementations for LocalVariableTypeInfoVisitor. @Override public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) { localVariableTypeInfo.u2signatureIndex = updateDescriptor(clazz, localVariableTypeInfo.u2signatureIndex); } // Implementations for AnnotationVisitor. @Override public void visitAnnotation(Clazz clazz, Annotation annotation) { annotation.u2typeIndex = updateDescriptor(clazz, annotation.u2typeIndex); annotation.elementValuesAccept(clazz, this); } // Implementations for ElementValueVisitor. @Override public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue) {} @Override public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) { enumConstantElementValue.u2typeNameIndex = updateDescriptor(clazz, enumConstantElementValue.u2typeNameIndex); } @Override public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) { String className = classElementValue.getClassName(clazz); String newClassName = replaceClassName(clazz, className); if (!newClassName.equals(className)) { classModified = true; classElementValue.u2classInfoIndex = constantPoolEditor.addUtf8Constant(newClassName); } } @Override public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) { annotationElementValue.annotationAccept(clazz, this); } @Override public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) { arrayElementValue.elementValuesAccept(clazz, annotation, this); } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKEINTERFACE: case Instruction.OP_INVOKESTATIC: this.referencingOffset = offset; this.referencingMethod = method; this.instructionReplaced = false; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); if (instructionReplaced && extraInstructionVisitor != null) { extraInstructionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); } break; case Instruction.OP_PUTFIELD: case Instruction.OP_GETFIELD: case Instruction.OP_PUTSTATIC: case Instruction.OP_GETSTATIC: this.referencingOffset = offset; this.referencingMethod = method; this.instructionReplaced = false; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); if (instructionReplaced && extraInstructionVisitor != null) { extraInstructionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); } break; } } // Implementations for ConstantVisitor. @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} @Override public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { String className = classConstant.getName(clazz); String newClassName = replaceClassName(clazz, className); if (!newClassName.equals(className)) { classConstant.u2nameIndex = constantPoolEditor.addUtf8Constant(newClassName); classModified = true; } } @Override public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { String name = fieldrefConstant.getName(clazz); String desc = fieldrefConstant.getType(clazz); String newDesc = replaceDescriptor(clazz, desc); if (!newDesc.equals(desc)) { fieldrefConstant.u2nameAndTypeIndex = constantPoolEditor.addNameAndTypeConstant(name, newDesc); classModified = true; } } @Override public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { if (!replaceMethodInvocation(referencingOffset, clazz, referencingMethod, anyMethodrefConstant)) { // If the method invocation was not replaced, we still // have to replace the descriptor if necessary. String name = anyMethodrefConstant.getName(clazz); String desc = anyMethodrefConstant.getType(clazz); String newDesc = replaceDescriptor(clazz, desc); if (!newDesc.equals(desc)) { anyMethodrefConstant.u2nameAndTypeIndex = constantPoolEditor.addNameAndTypeConstant(name, newDesc); classModified = true; } } } // Private utility methods. /** * Checks all the configured type replacements and replaces the given * class name accordingly. */ private String replaceClassName(Clazz clazz, String className) { for (TypeReplacement typeReplacement : typeReplacements) { String newClassName = typeReplacement.matchesClassName(className) ? typeReplacement.replaceClassName(clazz, className) : null; if (newClassName != null) { return newClassName; } } return className; } /** * Replaces all class types that appear in the given descriptor. */ private String replaceDescriptor(Clazz clazz, String descriptor) { DescriptorClassEnumeration descriptorClassEnumeration = new DescriptorClassEnumeration(descriptor); StringBuilder newDescriptorBuilder = new StringBuilder(descriptor.length()); newDescriptorBuilder.append(descriptorClassEnumeration.nextFluff()); while (descriptorClassEnumeration.hasMoreClassNames()) { String className = descriptorClassEnumeration.nextClassName(); boolean isInnerClassName = descriptorClassEnumeration.isInnerClassName(); String fluff = descriptorClassEnumeration.nextFluff(); // Strip the outer class name, if it's an inner class. if (isInnerClassName) { className = className.substring(className.lastIndexOf(TypeConstants.INNER_CLASS_SEPARATOR)+1); } newDescriptorBuilder.append(replaceClassName(clazz, className)); newDescriptorBuilder.append(fluff); } return newDescriptorBuilder.toString(); } /** * Returns an updated descriptor index if the descriptor * has changed. */ private int updateDescriptor(Clazz clazz, int descriptorIndex) { String descriptor = clazz.getString(descriptorIndex); String newDescriptor = replaceDescriptor(clazz, descriptor); if (!newDescriptor.equals(descriptor)) { classModified = true; return constantPoolEditor.addUtf8Constant(newDescriptor); } else { return descriptorIndex; } } /** * Checks if the instruction at the given offset has to be replaced and * modifies the code attribute accordingly. */ private boolean replaceMethodInvocation(int offset, Clazz clazz, Method method, AnyMethodrefConstant anyMethodrefConstant) { for (MethodReplacement methodReplacement : methodReplacements) { if (methodReplacement.matches(clazz, anyMethodrefConstant)) { methodReplacement.replaceInstruction(offset, clazz, method, anyMethodrefConstant); classModified = true; instructionReplaced = true; return true; } } return false; } // Private helper classes. /** * Abstract base class for type and method replacement helper classes. * Contains useful methods to avoid duplication. */ private abstract class AbstractReplacement { boolean isStatic(Method method) { return (method.getAccessFlags() & AccessConstants.STATIC) != 0; } boolean isDefaultMethod(Clazz clazz, Method method) { return isInterface(clazz) && (method.getAccessFlags() & AccessConstants.ABSTRACT) == 0; } boolean isInterface(Clazz clazz) { return (clazz.getAccessFlags() & AccessConstants.INTERFACE) != 0; } Clazz findReferencedClass(String className) { Clazz clazz = programClassPool.getClass(className); return clazz != null ? clazz : libraryClassPool.getClass(className); } Method findReferencedMethod(Clazz clazz, String methodName, String methodDescriptor) { return clazz.findMethod(methodName, methodDescriptor); } String getReplacement(String original, String actual, String replacement) { if (replacement.contains("<1>")) { if (original.equals("") || original.equals("")) { return actual; } int wildcardIndex = original.indexOf("*"); if (wildcardIndex != -1) { String match = actual.substring(wildcardIndex); int replacementIndex = replacement.indexOf("<1>"); return replacement.substring(0, replacementIndex) + match; } else { return original; } } else { return replacement; } } } /** * A helper class to define a needed method invocation replacement in an efficient way. */ protected class MethodReplacement extends AbstractReplacement { final String matchingClassName; final String matchingMethodName; final String matchingMethodDesc; final String replacementClassName; final String replacementMethodName; final String replacementMethodDesc; final StringMatcher classNameMatcher; final StringMatcher methodNameMatcher; final StringMatcher descMatcher; MethodReplacement(String className, String methodName, String methodDesc, String replacementClassName, String replacementMethodName, String replacementMethodDesc) { this.matchingClassName = className; this.matchingMethodName = methodName; this.matchingMethodDesc = methodDesc; this.replacementClassName = replacementClassName; this.replacementMethodName = replacementMethodName; this.replacementMethodDesc = replacementMethodDesc; classNameMatcher = new ClassNameParser(null).parse(matchingClassName); methodNameMatcher = new NameParser().parse(matchingMethodName); descMatcher = matchingMethodDesc.equals("**") ? new ConstantMatcher(true) : new ClassNameParser(null).parse(matchingMethodDesc); } private boolean isValid() { return replacementClassName.contains("*") || replacementClassName.contains("<1>") || findReferencedClass(replacementClassName) != null; } private String getDescReplacement(String original, String actual, String replacement) { if (matchingMethodName.equals("")) { // Extend the replacement descriptor. String replacedDesc = getReplacement(original, actual, replacement); return "(" + ClassUtil.internalTypeFromClassName(matchingClassName) + replacedDesc.substring(1); } else { return getReplacement(original, actual, replacement); } } boolean matches(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { String className = anyMethodrefConstant.getClassName(clazz); String methodName = anyMethodrefConstant.getName(clazz); String methodDesc = anyMethodrefConstant.getType(clazz); // Get the referenced class for the matching className. // Might be null for wildcard classNames. Clazz referencedMatchingClass = findReferencedClass(matchingClassName); Clazz referencedClass = anyMethodrefConstant.referencedClass; if (referencedClass == null) { // Might happen if the project is not setup correctly. // The class to be replaced is not present. return false; } Method referencedMethod = anyMethodrefConstant.referencedMethod; if (referencedMethod == null) { // Might happen if the project is not setup correctly. // The method to be replaced is not present. return false; } return classPatternMatches(className, referencedClass, referencedMatchingClass) && methodPatternMatches(methodName, referencedClass, referencedMethod) && descPatternMatches(methodDesc); } private boolean classPatternMatches(String className, Clazz referencedClazz, Clazz referencedMatchingClass) { return classNameMatcher.matches(className) || (referencedClazz != null && referencedClazz.extendsOrImplements(referencedMatchingClass)); } private boolean methodPatternMatches(String methodName, Clazz referencedClass, Method referencedMethod) { return methodNameMatcher.matches(methodName) || // or the method is a default method and the pattern matches all default methods (matchingMethodName.equals("") && isDefaultMethod(referencedClass, referencedMethod)) || // or the method is static and the pattern matches all static methods (matchingMethodName.equals("") && isStatic(referencedMethod)); } private boolean descPatternMatches(String methodDesc) { return descMatcher.matches(methodDesc); } void replaceInstruction(int offset, Clazz clazz, Method method, AnyMethodrefConstant anyMethodrefConstant) { String className = getReplacement(matchingClassName, anyMethodrefConstant.getClassName(clazz), replacementClassName); String methodName = getReplacement(matchingMethodName, anyMethodrefConstant.getName(clazz), replacementMethodName); String methodDesc = getDescReplacement(matchingMethodDesc, anyMethodrefConstant.getType(clazz), replacementMethodDesc); methodDesc = replaceDescriptor(clazz, methodDesc); Clazz referencedClass = findReferencedClass(className); if (referencedClass == null) { // Might happen if the project is not setup correctly. // The class to be replaced is not present. return; } Method referencedMethod = findReferencedMethod(referencedClass, methodName, methodDesc); if (referencedMethod == null) { warningPrinter.print(clazz.getName(), className, String.format("Warning: could not find replacement method '%s.%s(%s)',\n" + " not converting method instruction at offset %d " + "in method '%s.%s(%s)'.", ClassUtil.externalClassName(className), methodName, ClassUtil.externalMethodArguments(methodDesc), offset, ClassUtil.externalClassName(clazz.getName()), method.getName(clazz), ClassUtil.externalMethodArguments(method.getDescriptor(clazz)))); return; } boolean isInterfaceMethod = isInterface(referencedClass); byte replacementInstructionOpcode = isStatic(referencedMethod) ? Instruction.OP_INVOKESTATIC : isInterfaceMethod ? Instruction.OP_INVOKEINTERFACE : Instruction.OP_INVOKEVIRTUAL; int methodConstant = isInterfaceMethod ? constantPoolEditor.addInterfaceMethodrefConstant(className, methodName, methodDesc, referencedClass, referencedMethod) : constantPoolEditor.addMethodrefConstant(className, methodName, methodDesc, referencedClass, referencedMethod); codeAttributeEditor.replaceInstruction(offset, new ConstantInstruction(replacementInstructionOpcode, methodConstant)); logger.debug("Replacing instruction at offset %d: %s.%s%s -> %s.%s%s", offset, anyMethodrefConstant.getClassName(clazz), anyMethodrefConstant.getName(clazz), anyMethodrefConstant.getType(clazz), className, methodName, methodDesc ); } } private class MissingMethodReplacement extends MethodReplacement { MissingMethodReplacement(String className, String methodName, String methodDesc) { super(className, methodName, methodDesc, null, null, null); } boolean isValid() { return false; } void replaceInstruction(int offset, Clazz clazz, Method method, RefConstant refConstant) { String className = refConstant.getClassName(clazz); String methodName = refConstant.getName(clazz); String methodDesc = refConstant.getType(clazz); warningPrinter.print(clazz.getName(), String.format("Warning: no replacement available for '%s.%s(%s)'\n" + " found at offset %d in method '%s.%s(%s)'.", ClassUtil.externalClassName(className), methodName, ClassUtil.externalMethodArguments(methodDesc), offset, ClassUtil.externalClassName(clazz.getName()), method.getName(clazz), ClassUtil.externalMethodArguments(method.getDescriptor(clazz)))); } } /** * A helper class to define a needed type replacement in an efficient way. */ protected class TypeReplacement extends AbstractReplacement { final String matchingClassName; final String replacementClassName; final StringMatcher classNameMatcher; TypeReplacement(String matchingClassName, String replacementClassName) { this.matchingClassName = matchingClassName; this.replacementClassName = replacementClassName; this.classNameMatcher = new ClassNameParser(null).parse(matchingClassName); } boolean isValid() { return replacementClassName.contains("*") || replacementClassName.contains("<1>") || findReferencedClass(replacementClassName) != null; } boolean matchesClassName(String className) { return classNameMatcher.matches(className); } String replaceClassName(Clazz clazz, String className) { return getReplacement(matchingClassName, className, replacementClassName); } } private class MissingTypeReplacement extends TypeReplacement { MissingTypeReplacement(String className) { super(className, null); } boolean isValid() { return false; } String replaceClassName(Clazz clazz, String className) { warningPrinter.print(clazz.getName(), String.format("Warning: no replacement available for class '%s'\n" + " found in class '%s'.", ClassUtil.externalClassName(className), ClassUtil.externalClassName(clazz.getName()))); return className; } } } ================================================ FILE: base/src/main/java/proguard/backport/Backporter.java ================================================ /* * 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.backport; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.*; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.Constant; import proguard.classfile.editor.*; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionCounter; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.pass.Pass; import java.io.IOException; /** * This pass backports classes to the specified targetClassVersion. * * @author Thomas Neidhart */ public class Backporter implements Pass { private static final Logger logger = LogManager.getLogger(Backporter.class); private final Configuration configuration; public Backporter(Configuration configuration) { this.configuration = configuration; } @Override public void execute(AppView appView) throws IOException { int targetClassVersion = configuration.targetClassVersion; logger.info("Backporting class files..."); // Clean up any previous processing info. appView.programClassPool.classesAccept(new ClassCleaner()); appView.libraryClassPool.classesAccept(new ClassCleaner()); final InstructionCounter replacedStringConcatCounter = new InstructionCounter(); final ClassCounter lambdaExpressionCounter = new ClassCounter(); final MemberCounter staticInterfaceMethodCounter = new MemberCounter(); final MemberCounter defaultInterfaceMethodCounter = new MemberCounter(); final InstructionCounter replacedMethodCallCounter = new InstructionCounter(); final InstructionCounter replacedStreamsMethodCallCounter = new InstructionCounter(); final InstructionCounter replacedTimeMethodCallCounter = new InstructionCounter(); if (targetClassVersion < VersionConstants.CLASS_VERSION_1_9) { // Convert indy string concatenations to StringBuilder chains CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true); appView.programClassPool.classesAccept( new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_9, new AllAttributeVisitor( new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, new AttributeToClassVisitor( new MultiClassVisitor( new AllMethodVisitor( new AllAttributeVisitor( new PeepholeEditor(codeAttributeEditor, // Replace the indy instructions related to String concatenation. new StringConcatenationConverter(replacedStringConcatCounter, codeAttributeEditor))) ), // Clean up unused bootstrap methods and their dangling constants. new BootstrapMethodsAttributeShrinker(), // Initialize new references to StringBuilder. new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool) )))))); } if (targetClassVersion < VersionConstants.CLASS_VERSION_1_8) { // Collect all classes with BootstrapMethod attributes, // and convert lambda expressions and method references. ClassPool filteredClasses = new ClassPool(); appView.programClassPool.classesAccept( new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_8, new AllAttributeVisitor( new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, new AttributeToClassVisitor( new ClassPoolFiller(filteredClasses)))))); // Note: we visit the filtered classes in a separate step // because we modify the programClassPool while converting filteredClasses.classesAccept( new MultiClassVisitor( // Replace the indy instructions related to lambda expressions. new LambdaExpressionConverter(appView.programClassPool, appView.libraryClassPool, appView.extraDataEntryNameMap, lambdaExpressionCounter), // Clean up unused bootstrap methods and their dangling constants. new BootstrapMethodsAttributeShrinker(), // Re-initialize references. new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool) )); // Remove static and default methods from interfaces if the // target version is < 1.8. The dalvik format 037 has native // support for default methods. The dalvik format specification // does not explicitly mention static interface methods, although // they seem to work correctly. ClassPool interfaceClasses = new ClassPool(); appView.programClassPool.classesAccept( new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_8, new ClassAccessFilter(AccessConstants.INTERFACE, 0, new ClassPoolFiller(interfaceClasses)))); ClassPool modifiedClasses = new ClassPool(); ClassVisitor modifiedClassCollector = new ClassPoolFiller(modifiedClasses); interfaceClasses.classesAccept( new MultiClassVisitor( new StaticInterfaceMethodConverter(appView.programClassPool, appView.libraryClassPool, appView.extraDataEntryNameMap, modifiedClassCollector, staticInterfaceMethodCounter), new DefaultInterfaceMethodConverter(modifiedClassCollector, defaultInterfaceMethodCounter) )); // Re-Initialize references in modified classes. modifiedClasses.classesAccept( new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool)); } if (targetClassVersion < VersionConstants.CLASS_VERSION_1_7) { // Replace / remove method calls only available in Java 7+. InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(appView.programClassPool, appView.libraryClassPool); Instruction[][][] instructions = new Instruction[][][] { // Replace Objects.requireNonNull(...) with Object.getClass(). // Starting in JDK 9, javac uses {@code requireNonNull} for // synthetic null-checks // (see // JDK-8074306). { ____.invokestatic("java/util/Objects", "requireNonNull", "(Ljava/lang/Object;)Ljava/lang/Object;").__(), ____.dup() .invokevirtual(ClassConstants.NAME_JAVA_LANG_OBJECT, ClassConstants.METHOD_NAME_OBJECT_GET_CLASS, ClassConstants.METHOD_TYPE_OBJECT_GET_CLASS) .pop().__() }, // Remove Throwable.addSuppressed(...). { ____.invokevirtual("java/util/Throwable", "addSuppressed", "(Ljava/lang/Throwable;)V").__(), ____.pop() // the suppressed exception .pop().__() // the original exception } }; Constant[] constants = ____.constants(); CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); appView.programClassPool.classesAccept( new AllMethodVisitor( new AllAttributeVisitor( new PeepholeEditor(null, codeAttributeEditor, new InstructionSequencesReplacer(constants, instructions, null, codeAttributeEditor, replacedMethodCallCounter))))); } if (targetClassVersion < VersionConstants.CLASS_VERSION_1_8) { // Sanity check: if the streamsupport library is not found in the // classpools do not try to backport. ClassCounter streamSupportClasses = new ClassCounter(); ClassVisitor streamSupportVisitor = new ClassNameFilter("java8/**", streamSupportClasses); appView.programClassPool.classesAccept(streamSupportVisitor); appView.libraryClassPool.classesAccept(streamSupportVisitor); if (streamSupportClasses.getCount() > 0) { WarningPrinter streamSupportWarningPrinter = new WarningLogger(logger, configuration.warn); ClassPool modifiedClasses = new ClassPool(); ClassVisitor modifiedClassCollector = new ClassPoolFiller(modifiedClasses); appView.programClassPool.classesAccept( // Do not process classes of the stream support library itself. new ClassNameFilter("!java8/**", new StreamSupportConverter(appView.programClassPool, appView.libraryClassPool, streamSupportWarningPrinter, modifiedClassCollector, replacedStreamsMethodCallCounter))); // Re-Initialize references in modified classes. modifiedClasses.classesAccept( new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool)); int conversionWarningCount = streamSupportWarningPrinter.getWarningCount(); if (conversionWarningCount > 0) { logger.warn("Warning: there were {} Java 8 stream API method calls that could not be backported.", conversionWarningCount); logger.warn(" You should check if a your project setup is correct (compileSdkVersion, streamsupport dependency)."); logger.warn(" For more information, consult the section \'Integration->Gradle Plugin->Java 8 stream API support\' in our manual"); } } } if (targetClassVersion < VersionConstants.CLASS_VERSION_1_8) { // Sanity check: if the threetenbp library is not found in the // classpools do not try to backport. ClassCounter threetenClasses = new ClassCounter(); ClassVisitor threetenClassVisitor = new ClassNameFilter("org/threeten/bp/**", threetenClasses); appView.programClassPool.classesAccept(threetenClassVisitor); appView.libraryClassPool.classesAccept(threetenClassVisitor); if (threetenClasses.getCount() > 0) { WarningPrinter threetenWarningPrinter = new WarningLogger(logger, configuration.warn); ClassPool modifiedClasses = new ClassPool(); ClassVisitor modifiedClassCollector = new ClassPoolFiller(modifiedClasses); appView.programClassPool.classesAccept( // Do not process classes of the threeten library itself. new ClassNameFilter("!org/threeten/bp/**", new JSR310Converter(appView.programClassPool, appView.libraryClassPool, threetenWarningPrinter, modifiedClassCollector, replacedTimeMethodCallCounter))); // Re-Initialize references in modified classes. modifiedClasses.classesAccept( new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool)); int conversionWarningCount = threetenWarningPrinter.getWarningCount(); if (conversionWarningCount > 0) { logger.warn("Warning: there were {} Java 8 time API method calls that could not be backported.", conversionWarningCount); logger.warn(" You should check if a your project setup is correct (compileSdkVersion, threetenbp dependency)."); logger.warn(" For more information, consult the section \'Integration->Gradle Plugin->Java 8 time API support\' in our manual"); } } } if (targetClassVersion != 0) { // Set the class version of all classes in the program ClassPool // to the specified target version. This is needed to perform // optimization on the backported + generated classes. appView.programClassPool.classesAccept(new ClassVersionSetter(targetClassVersion)); } logger.info(" Number of converted string concatenations: {}", replacedStringConcatCounter.getCount()); logger.info(" Number of converted lambda expressions: {}", lambdaExpressionCounter.getCount()); logger.info(" Number of converted static interface methods: {}", staticInterfaceMethodCounter.getCount()); logger.info(" Number of converted default interface methods: {}", defaultInterfaceMethodCounter.getCount()); logger.info(" Number of replaced Java 7+ method calls: {}", replacedMethodCallCounter.getCount()); logger.info(" Number of replaced Java 8 stream method calls: {}", replacedStreamsMethodCallCounter.getCount()); logger.info(" Number of replaced Java 8 time method calls: {}", replacedTimeMethodCallCounter.getCount()); } } ================================================ FILE: base/src/main/java/proguard/backport/DefaultInterfaceMethodConverter.java ================================================ /* * 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.backport; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.*; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.*; import proguard.classfile.visitor.*; import proguard.util.*; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; /** * This ClassVisitor moves all default interface methods in the visited * interfaces to concrete implementations. * * @author Thomas Neidhart */ public class DefaultInterfaceMethodConverter implements ClassVisitor, // Implementation interfaces. AttributeVisitor { private final ClassVisitor modifiedClassVisitor; private final MemberVisitor extraMemberVisitor; // Fields acting as parameters and return values for the visitor methods. private final Set implClasses = new LinkedHashSet(); private boolean hasDefaultMethods; public DefaultInterfaceMethodConverter(ClassVisitor modifiedClassVisitor, MemberVisitor extraMemberVisitor) { this.modifiedClassVisitor = modifiedClassVisitor; this.extraMemberVisitor = extraMemberVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { hasDefaultMethods = false; implClasses.clear(); // Collect all implementations of the interface. programClass.hierarchyAccept(false, false, false, true, new ProgramClassFilter( // Ignore other interfaces that extend this one. new ClassAccessFilter(0, AccessConstants.INTERFACE, new ClassCollector(implClasses)))); programClass.accept( new AllMethodVisitor( new MemberAccessFilter(0, AccessConstants.STATIC, new AllAttributeVisitor(this)))); if (hasDefaultMethods) { // Shrink the constant pool of unused constants. programClass.accept(new ConstantPoolShrinker()); } } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { hasDefaultMethods = true; ProgramClass interfaceClass = (ProgramClass) clazz; ProgramMethod defaultMethod = (ProgramMethod) method; for (Clazz implClass : implClasses) { ProgramClass targetClass = (ProgramClass) implClass; // Add the default method to the implementing class // if necessary. if (!hasInheritedMethod(targetClass, defaultMethod.getName(interfaceClass), defaultMethod.getDescriptor(interfaceClass))) { defaultMethod.accept(interfaceClass, new MemberAdder(targetClass)); targetClass.accept(modifiedClassVisitor); } // Add the default method as a different method and adapt // super invocations to it, if necessary. if (callsDefaultMethodUsingSuper(targetClass, interfaceClass, defaultMethod)) { replaceDefaultMethodInvocation(targetClass, interfaceClass, defaultMethod); targetClass.accept(modifiedClassVisitor); } } // Remove the code attribute from the method and // add make it abstract. defaultMethod.accept(interfaceClass, new MultiMemberVisitor( new NamedAttributeDeleter(Attribute.CODE), new MemberAccessFlagSetter(AccessConstants.ABSTRACT) )); // Call extra visitor for each visited default method. if (extraMemberVisitor != null) { defaultMethod.accept(interfaceClass, extraMemberVisitor); } } // Small utility methods. private boolean hasInheritedMethod(Clazz clazz, String methodName, String methodDescriptor) { MemberCounter counter = new MemberCounter(); clazz.hierarchyAccept(true, true, false, false, new NamedMethodVisitor(methodName, methodDescriptor, counter)); return counter.getCount() > 0; } /** * Returns true if any method of the given class * calls Interface.super.defaultMethod(...). */ private boolean callsDefaultMethodUsingSuper(Clazz clazz, Clazz interfaceClass, Method defaultMethod) { final AtomicBoolean foundInvocation = new AtomicBoolean(false); clazz.accept( new AllMethodVisitor( new AllAttributeVisitor( new AllInstructionVisitor( new SuperInvocationInstructionMatcher(interfaceClass, defaultMethod) { @Override public void superInvocation(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, CodeAttributeEditor codeAttributeEditor) { foundInvocation.set(true); } })))); return foundInvocation.get(); } /** * Replaces any super calls to the given default interface method * in the target class. The default method is copied to the target * class and the invoke is updated accordingly. */ private void replaceDefaultMethodInvocation(ProgramClass targetClass, ProgramClass interfaceClass, ProgramMethod interfaceMethod) { // Copy the interface method to the target class, with an updated name. StringFunction memberNameFunction = new PrefixAddingStringFunction("default$"); interfaceMethod.accept(interfaceClass, new MemberAdder(targetClass, memberNameFunction, null)); String targetMethodName = memberNameFunction.transform(interfaceMethod.getName(interfaceClass)); // Update invocations of the method inside the target class. String descriptor = interfaceMethod.getDescriptor(interfaceClass); Method targetMethod = targetClass.findMethod(targetMethodName, descriptor); ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(targetClass); final int constantIndex = constantPoolEditor.addMethodrefConstant(targetClass, targetMethod); targetClass.accept( new AllMethodVisitor( new AllAttributeVisitor( new SuperInvocationInstructionMatcher(interfaceClass, interfaceMethod) { @Override public void superInvocation(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, CodeAttributeEditor codeAttributeEditor) { Instruction instruction = new ConstantInstruction(Instruction.OP_INVOKEVIRTUAL, constantIndex); codeAttributeEditor.replaceInstruction(offset, instruction); } }))); } /** * This InstructionVisitor will call the {@code superInvocation(...)} method * for any encountered INVOKESPECIAL instruction whose associated * constant is an InterfaceMethodRefConstant and matches the given * referenced class and method. */ private static class SuperInvocationInstructionMatcher implements AttributeVisitor, InstructionVisitor, ConstantVisitor { private final Clazz referencedClass; private final Method referencedMethod; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); private boolean matchingInvocation; public SuperInvocationInstructionMatcher(Clazz referencedClass, Method referencedMethod) { this.referencedClass = referencedClass; this.referencedMethod = referencedMethod; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Set up the code attribute editor. codeAttributeEditor.reset(codeAttribute.u4codeLength); // Find the peephole optimizations. codeAttribute.instructionsAccept(clazz, method, this); // Apply the peephole optimizations. codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_INVOKESPECIAL: matchingInvocation = false; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); if (matchingInvocation) { superInvocation(clazz, method, codeAttribute, offset, codeAttributeEditor); } break; } } // Implementations for ConstantVisitor. @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} @Override public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) { if (interfaceMethodrefConstant.referencedClass == referencedClass && interfaceMethodrefConstant.referencedMethod == referencedMethod) { matchingInvocation = true; } } /** * The callback method which will be called for each detected super invocation * of the specified interface method. */ public void superInvocation(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, CodeAttributeEditor codeAttributeEditor) {} } } ================================================ FILE: base/src/main/java/proguard/backport/JSR310Converter.java ================================================ /* * 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.backport; import proguard.classfile.ClassPool; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.WarningPrinter; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor will replace any occurrence of java.time.** related methods / types * that have been introduced in Java 8 to the threetenbp library. * * @author Thomas Neidhart */ public class JSR310Converter extends AbstractAPIConverter { /** * Create a new JSR310Converter instance. */ public JSR310Converter(ClassPool programClassPool, ClassPool libraryClassPool, WarningPrinter warningPrinter, ClassVisitor modifiedClassVisitor, InstructionVisitor extraInstructionVisitor) { super(programClassPool, libraryClassPool, warningPrinter, modifiedClassVisitor, extraInstructionVisitor); TypeReplacement[] typeReplacements = new TypeReplacement[] { // java.time package has been added in Java 8 replace("java/time/**", "org/threeten/bp/<1>"), }; MethodReplacement[] methodReplacements = new MethodReplacement[] { // all classes in java.time.** are converted to // org.threeeten.bp.**. replace("java/time/**", "**", "**", "org/threeten/bp/<1>", "<1>", "<1>"), }; setTypeReplacements(typeReplacements); setMethodReplacements(methodReplacements); } } ================================================ FILE: base/src/main/java/proguard/backport/LambdaExpression.java ================================================ /* * 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.backport; import proguard.classfile.*; import proguard.classfile.attribute.BootstrapMethodInfo; import proguard.classfile.constant.MethodHandleConstant; import proguard.classfile.util.*; /** * A small helper class that captures useful information * about a lambda expression as encountered in a class file. * * @author Thomas Neidhart */ public class LambdaExpression { // The referenced class of the lambda expression. public ProgramClass referencedClass; // The referenced bootstrap method index. public int bootstrapMethodIndex; // The referenced bootstrap method info. public BootstrapMethodInfo bootstrapMethodInfo; // The lambda factory method type. public String factoryMethodDescriptor; // The implemented interfaces of the Lambda expression. public String[] interfaces; // The additional bridge method descriptors to be added. public String[] bridgeMethodDescriptors; // The name and descriptor of the implemented interface method. public String interfaceMethod; public String interfaceMethodDescriptor; // Information regarding the invoked method. public int invokedReferenceKind; public String invokedClassName; public String invokedMethodName; public String invokedMethodDesc; public Clazz referencedInvokedClass; public Method referencedInvokedMethod; // The created lambda class. public ProgramClass lambdaClass; /** * Creates a new initialized LambdaExpression (except for the lambdaClass). */ public LambdaExpression(ProgramClass referencedClass, int bootstrapMethodIndex, BootstrapMethodInfo bootstrapMethodInfo, String factoryMethodDescriptor, String[] interfaces, String[] bridgeMethodDescriptors, String interfaceMethod, String interfaceMethodDescriptor, int invokedReferenceKind, String invokedClassName, String invokedMethodName, String invokedMethodDesc, Clazz referencedInvokedClass, Method referencedInvokedMethod) { this.referencedClass = referencedClass; this.bootstrapMethodIndex = bootstrapMethodIndex; this.bootstrapMethodInfo = bootstrapMethodInfo; this.factoryMethodDescriptor = factoryMethodDescriptor; this.interfaces = interfaces; this.bridgeMethodDescriptors = bridgeMethodDescriptors; this.interfaceMethod = interfaceMethod; this.interfaceMethodDescriptor = interfaceMethodDescriptor; this.invokedReferenceKind = invokedReferenceKind; this.invokedClassName = invokedClassName; this.invokedMethodName = invokedMethodName; this.invokedMethodDesc = invokedMethodDesc; this.referencedInvokedClass = referencedInvokedClass; this.referencedInvokedMethod = referencedInvokedMethod; } /** * Returns the class name of the converted anonymous class. */ public String getLambdaClassName() { return String.format("%s$$Lambda$%d", referencedClass.getName(), bootstrapMethodIndex); } public String getConstructorDescriptor() { if (isStateless()) { return ClassConstants.METHOD_TYPE_INIT; } else { int endIndex = factoryMethodDescriptor.indexOf(TypeConstants.METHOD_ARGUMENTS_CLOSE); return factoryMethodDescriptor.substring(0, endIndex + 1) + TypeConstants.VOID; } } /** * Returns whether the lambda expression is serializable. */ public boolean isSerializable() { for (String interfaceName : interfaces) { if (ClassConstants.NAME_JAVA_IO_SERIALIZABLE.equals(interfaceName)) { return true; } } return false; } /** * Returns whether the lambda expression is actually a method reference. */ public boolean isMethodReference() { return !isLambdaMethod(invokedMethodName); } /** * Returns whether the lambda expression is stateless. */ public boolean isStateless() { // The lambda expression is stateless if the factory method does // not have arguments. return ClassUtil.internalMethodParameterCount(factoryMethodDescriptor) == 0; } /** * Returns whether the invoked method is a static interface method. */ public boolean invokesStaticInterfaceMethod() { // We assume unknown classes are not interfaces. return invokedReferenceKind == MethodHandleConstant.REF_INVOKE_STATIC && referencedInvokedClass != null && (referencedInvokedClass.getAccessFlags() & AccessConstants.INTERFACE) != 0; } /** * Returns whether the invoked method is a non-static, private synthetic * method in an interface. */ boolean referencesPrivateSyntheticInterfaceMethod() { return (referencedInvokedClass .getAccessFlags() & AccessConstants.INTERFACE) != 0 && (referencedInvokedMethod.getAccessFlags() & (AccessConstants.PRIVATE | AccessConstants.SYNTHETIC)) != 0 ; } /** * Returns whether an accessor method is needed to access * the invoked method from the lambda class. */ public boolean needsAccessorMethod() { // We assume unknown classes don't need an accessor method. return referencedInvokedClass != null && new MemberFinder().findMethod(lambdaClass, referencedInvokedClass, invokedMethodName, invokedMethodDesc) == null; } /** * Returns whether the lambda expression is a method reference * to a private constructor. */ public boolean referencesPrivateConstructor() { return invokedReferenceKind == MethodHandleConstant.REF_NEW_INVOKE_SPECIAL && ClassConstants.METHOD_NAME_INIT.equals(invokedMethodName) && (referencedInvokedMethod.getAccessFlags() & AccessConstants.PRIVATE) != 0; } // Small Utility methods. private static final String LAMBDA_METHOD_PREFIX = "lambda$"; private static boolean isLambdaMethod(String methodName) { return methodName.startsWith(LAMBDA_METHOD_PREFIX); } } ================================================ FILE: base/src/main/java/proguard/backport/LambdaExpressionCollector.java ================================================ /* * 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.backport; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.*; import proguard.classfile.util.*; import proguard.classfile.visitor.ClassVisitor; import proguard.util.ArrayUtil; import java.util.*; /** * This ClassVisitor collects all lambda expressions that are defined in * a visited class. * * @author Thomas Neidhart */ public class LambdaExpressionCollector implements ClassVisitor, // Implementation interfaces. ConstantVisitor, AttributeVisitor, BootstrapMethodInfoVisitor { private final Map lambdaExpressions; private InvokeDynamicConstant referencedInvokeDynamicConstant; private int referencedBootstrapMethodIndex; private Clazz referencedInvokedClass; private Method referencedInvokedMethod; public LambdaExpressionCollector(Map lambdaExpressions) { this.lambdaExpressions = lambdaExpressions; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Visit any InvokeDynamic constant. programClass.constantPoolEntriesAccept( new ConstantTagFilter(Constant.INVOKE_DYNAMIC, this)); } // Implementations for ConstantVisitor. @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} @Override public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { referencedInvokeDynamicConstant = invokeDynamicConstant; referencedBootstrapMethodIndex = invokeDynamicConstant.getBootstrapMethodAttributeIndex(); clazz.attributesAccept(this); } @Override public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { referencedInvokedClass = anyMethodrefConstant.referencedClass; referencedInvokedMethod = anyMethodrefConstant.referencedMethod; } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { bootstrapMethodsAttribute.bootstrapMethodEntryAccept(clazz, referencedBootstrapMethodIndex, this); } // Implementations for BootstrapMethodInfoVisitor. @Override public void visitBootstrapMethodInfo(Clazz clazz, BootstrapMethodInfo bootstrapMethodInfo) { ProgramClass programClass = (ProgramClass) clazz; MethodHandleConstant bootstrapMethodHandle = (MethodHandleConstant) programClass.getConstant(bootstrapMethodInfo.u2methodHandleIndex); if (isLambdaMetaFactory(bootstrapMethodHandle.getClassName(clazz))) { String factoryMethodDescriptor = referencedInvokeDynamicConstant.getType(clazz); String interfaceClassName = ClassUtil.internalClassNameFromClassType(ClassUtil.internalMethodReturnType(factoryMethodDescriptor)); // Find the actual method that is being invoked. MethodHandleConstant invokedMethodHandle = (MethodHandleConstant) programClass.getConstant(bootstrapMethodInfo.u2methodArguments[1]); referencedInvokedClass = null; referencedInvokedMethod = null; clazz.constantPoolEntryAccept(invokedMethodHandle.u2referenceIndex, this); // Collect all the useful information. LambdaExpression lambdaExpression = new LambdaExpression(programClass, referencedBootstrapMethodIndex, bootstrapMethodInfo, factoryMethodDescriptor, new String[] { interfaceClassName }, new String[0], referencedInvokeDynamicConstant.getName(clazz), getMethodTypeConstant(programClass, bootstrapMethodInfo.u2methodArguments[0]).getType(clazz), invokedMethodHandle.getReferenceKind(), invokedMethodHandle.getClassName(clazz), invokedMethodHandle.getName(clazz), invokedMethodHandle.getType(clazz), referencedInvokedClass, referencedInvokedMethod); if (isAlternateFactoryMethod(bootstrapMethodHandle.getName(clazz))) { int flags = getIntegerConstant(programClass, bootstrapMethodInfo.u2methodArguments[3]); // For the alternate metafactory, the optional arguments start // at index 4. int argumentIndex = 4; if ((flags & BootstrapMethodInfo.FLAG_MARKERS) != 0) { int markerInterfaceCount = getIntegerConstant(programClass, bootstrapMethodInfo.u2methodArguments[argumentIndex++]); for (int i = 0; i < markerInterfaceCount; i++) { String interfaceName = programClass.getClassName(bootstrapMethodInfo.u2methodArguments[argumentIndex++]); lambdaExpression.interfaces = ArrayUtil.add(lambdaExpression.interfaces, lambdaExpression.interfaces.length, interfaceName); } } if ((flags & BootstrapMethodInfo.FLAG_BRIDGES) != 0) { int bridgeMethodCount = getIntegerConstant(programClass, bootstrapMethodInfo.u2methodArguments[argumentIndex++]); for (int i = 0; i < bridgeMethodCount; i++) { MethodTypeConstant methodTypeConstant = getMethodTypeConstant(programClass, bootstrapMethodInfo.u2methodArguments[argumentIndex++]); lambdaExpression.bridgeMethodDescriptors = ArrayUtil.add(lambdaExpression.bridgeMethodDescriptors, lambdaExpression.bridgeMethodDescriptors.length, methodTypeConstant.getType(programClass)); } } if ((flags & BootstrapMethodInfo.FLAG_SERIALIZABLE) != 0) { lambdaExpression.interfaces = ArrayUtil.add(lambdaExpression.interfaces, lambdaExpression.interfaces.length, ClassConstants.NAME_JAVA_IO_SERIALIZABLE); } } lambdaExpressions.put(referencedBootstrapMethodIndex, lambdaExpression); } } // Small utility methods private static final String NAME_JAVA_LANG_INVOKE_LAMBDA_METAFACTORY = "java/lang/invoke/LambdaMetafactory"; private static final String LAMBDA_ALTERNATE_METAFACTORY_METHOD = "altMetafactory"; private static boolean isLambdaMetaFactory(String className) { return NAME_JAVA_LANG_INVOKE_LAMBDA_METAFACTORY.equals(className); } private static boolean isAlternateFactoryMethod(String methodName) { return LAMBDA_ALTERNATE_METAFACTORY_METHOD.equals(methodName); } private static int getIntegerConstant(ProgramClass programClass, int constantIndex) { return ((IntegerConstant) programClass.getConstant(constantIndex)).getValue(); } private static MethodTypeConstant getMethodTypeConstant(ProgramClass programClass, int constantIndex) { return (MethodTypeConstant) programClass.getConstant(constantIndex); } } ================================================ FILE: base/src/main/java/proguard/backport/LambdaExpressionConverter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.backport; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.AccessConstants; import proguard.classfile.ClassConstants; import proguard.classfile.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.Member; import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramMethod; import proguard.classfile.TypeConstants; import proguard.classfile.VersionConstants; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.InvokeDynamicConstant; import proguard.classfile.constant.MethodHandleConstant; import proguard.classfile.editor.ClassBuilder; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.editor.CompactCodeAttributeComposer; import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.editor.InstructionSequenceBuilder; import proguard.classfile.editor.MemberRemover; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassSubHierarchyInitializer; import proguard.classfile.util.ClassSuperHierarchyInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.util.InternalTypeEnumeration; import proguard.classfile.visitor.AllMethodVisitor; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.MemberAccessFlagSetter; import proguard.classfile.visitor.MemberAccessSetter; import proguard.classfile.visitor.MemberVisitor; import proguard.classfile.visitor.MultiClassVisitor; import proguard.io.ExtraDataEntryNameMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; /** * This ClassVisitor converts all lambda expressions in the visited * classes to anonymous inner classes. * * @author Thomas Neidhart */ public class LambdaExpressionConverter implements ClassVisitor, // Implementation interfaces. MemberVisitor, AttributeVisitor, InstructionVisitor { private static final Logger logger = LogManager.getLogger(LambdaExpressionConverter.class); private static final String LAMBDA_SINGLETON_FIELD_NAME = "INSTANCE"; private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final ExtraDataEntryNameMap extraDataEntryNameMap; private final ClassVisitor extraClassVisitor; private final Map lambdaExpressionMap; private final CodeAttributeEditor codeAttributeEditor; private final MemberRemover memberRemover; public LambdaExpressionConverter(ClassPool programClassPool, ClassPool libraryClassPool, ExtraDataEntryNameMap extraDataEntryNameMap, ClassVisitor extraClassVisitor) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.extraDataEntryNameMap = extraDataEntryNameMap; this.extraClassVisitor = extraClassVisitor; this.lambdaExpressionMap = new HashMap<>(); this.codeAttributeEditor = new CodeAttributeEditor(true, true); this.memberRemover = new MemberRemover(); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { lambdaExpressionMap.clear(); programClass.accept(new LambdaExpressionCollector(lambdaExpressionMap)); if (!lambdaExpressionMap.isEmpty()) { logger.debug("LambdaExpressionConverter: converting lambda expressions in [{}]", programClass.getName()); for (LambdaExpression lambdaExpression : lambdaExpressionMap.values()) { ProgramClass lambdaClass = createLambdaClass(lambdaExpression); // Add the converted lambda class to the program class pool // and the injected class name map. programClassPool.addClass(lambdaClass); extraDataEntryNameMap.addExtraClassToClass(programClass, lambdaClass); if (extraClassVisitor != null) { extraClassVisitor.visitProgramClass(lambdaClass); } } // Replace all InvokeDynamic instructions. programClass.accept( new AllMethodVisitor( new AllAttributeVisitor( this))); // Initialize the hierarchy and references of all lambda classes. for (LambdaExpression lambdaExpression : lambdaExpressionMap.values()) { lambdaExpression.lambdaClass.accept( new MultiClassVisitor( new ClassSuperHierarchyInitializer(programClassPool, libraryClassPool), new ClassSubHierarchyInitializer(), new ClassReferenceInitializer(programClassPool, libraryClassPool) )); } // Remove deserialization hooks as they are no longer needed. programClass.methodsAccept(this); memberRemover.visitProgramClass(programClass); } } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttributeEditor.reset(codeAttribute.u4codeLength); codeAttribute.instructionsAccept(clazz, method, this); if (codeAttributeEditor.isModified()) { codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_INVOKEDYNAMIC) { ProgramClass programClass = (ProgramClass) clazz; InvokeDynamicConstant invokeDynamicConstant = (InvokeDynamicConstant) programClass.getConstant(constantInstruction.constantIndex); int bootstrapMethodIndex = invokeDynamicConstant.getBootstrapMethodAttributeIndex(); if (lambdaExpressionMap.containsKey(bootstrapMethodIndex)) { LambdaExpression lambdaExpression = lambdaExpressionMap.get(bootstrapMethodIndex); String lambdaClassName = lambdaExpression.getLambdaClassName(); InstructionSequenceBuilder builder = new InstructionSequenceBuilder(programClass); if (lambdaExpression.isStateless()) { logger.debug("LambdaExpressionConverter: {} -> getting static {}.{}", constantInstruction.toString(offset), lambdaClassName, LAMBDA_SINGLETON_FIELD_NAME); builder.getstatic(lambdaClassName, LAMBDA_SINGLETON_FIELD_NAME, ClassUtil.internalTypeFromClassName(lambdaClassName)); } else { logger.debug("LambdaExpressionConverter: {} -> new instance of {}", constantInstruction.toString(offset), lambdaClassName); int maxLocals = codeAttribute.u2maxLocals; String methodDescriptor = lambdaExpression.getConstructorDescriptor(); int parameterSize = ClassUtil.internalMethodParameterSize(methodDescriptor); // TODO: the special optimization in case there is only 1 // parameter has been disabled as the used stack // manipulation instructions might confuse the class // converter (testcase 1988). if (parameterSize == 1 && false) { // If only 1 parameter is captured by the lambda expression, // and it is a Category 1 value, we can avoid storing the // current stack to variables. builder.new_(lambdaClassName) .dup_x1() .swap() .invokespecial(lambdaClassName, ClassConstants.METHOD_NAME_INIT, methodDescriptor); } else { // More than 1 (or a Category 2) parameter is captured // by the lambda expression. We need to store the current // call stack to variables, create the lambda instance and // load the call stack again from the temporary variables. // Collect the argument types. InternalTypeEnumeration typeEnumeration = new InternalTypeEnumeration(methodDescriptor); List types = new ArrayList(); while(typeEnumeration.hasMoreTypes()) { types.add(typeEnumeration.nextType()); } // Store the current call stack in reverse order // into temporary variables. int variableIndex = maxLocals; ListIterator typeIterator = types.listIterator(types.size()); while (typeIterator.hasPrevious()) { String type = typeIterator.previous(); builder.store(variableIndex, type); variableIndex += ClassUtil.internalTypeSize(type); } // Create the lambda instance. builder.new_(lambdaClassName); builder.dup(); // Reconstruct the call stack by loading it from // the temporary variables. typeIterator = types.listIterator(); while (typeIterator.hasNext()) { String type = typeIterator.next(); int variableSize = ClassUtil.internalTypeSize(type); variableIndex -= variableSize; builder.load(variableIndex, type); } builder.invokespecial(lambdaClassName, ClassConstants.METHOD_NAME_INIT, methodDescriptor); } } codeAttributeEditor.replaceInstruction(offset, builder.instructions()); } } } // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) {} @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (isDeserializationHook(programClass, programMethod)) { memberRemover.visitProgramMethod(programClass, programMethod); } } // Small utility methods. private static final String METHOD_NAME_DESERIALIZE_LAMBDA = "$deserializeLambda$"; private static final String METHOD_TYPE_DESERIALIZE_LAMBDA = "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;"; private static boolean isDeserializationHook(Clazz clazz, Method method) { return method.getName(clazz) .equals(METHOD_NAME_DESERIALIZE_LAMBDA) && method.getDescriptor(clazz).equals(METHOD_TYPE_DESERIALIZE_LAMBDA) && hasFlag(method, AccessConstants.PRIVATE | AccessConstants.STATIC | AccessConstants.SYNTHETIC); } private static boolean hasFlag(Member member, int flag) { return (member.getAccessFlags() & flag) == flag; } private ProgramClass createLambdaClass(LambdaExpression lambdaExpression) { String lambdaClassName = lambdaExpression.getLambdaClassName(); logger.debug("LambdaExpressionConverter: creating lambda class [{}]", lambdaClassName); // Start creating the lambda class. ClassBuilder classBuilder = new ClassBuilder(VersionConstants.CLASS_VERSION_1_2, 0, lambdaClassName, ClassConstants.NAME_JAVA_LANG_OBJECT); // Add its interfaces. String[] interfaces = lambdaExpression.interfaces; for (String interfaceName : interfaces) { classBuilder.addInterface(interfaceName); } ProgramClass lambdaClass = classBuilder.getProgramClass(); // Store the created lambda class in the LambdaExpression // data structure for later use. lambdaExpression.lambdaClass = lambdaClass; // [DGD-968] When a lambda expression is called from a `default` // interface method, ensure that it is stateless and visible to the // lambda class instead of generating an accessor method. The method // will be properly backported by the {@link StaticInterfaceMethodConverter}. if (lambdaExpression.referencesPrivateSyntheticInterfaceMethod()) { fixInterfaceLambdaMethod(lambdaExpression.referencedClass, (ProgramMethod) lambdaExpression.referencedInvokedMethod, lambdaExpression); } else if (lambdaExpression.referencesPrivateConstructor() || lambdaExpression.needsAccessorMethod()) { // In case the invoked method can not be accessed directly // by the lambda class, add a synthetic accessor method. addAccessorMethod(lambdaExpression.referencedClass, lambdaExpression); } if (lambdaExpression.isStateless()) { completeStatelessLambdaClass(lambdaClass, lambdaExpression); } else { completeCapturingLambdaClass(lambdaClass, lambdaExpression); } if (lambdaExpression.bridgeMethodDescriptors.length > 0) { addBridgeMethods(lambdaClass, lambdaExpression); } return lambdaClass; } private void fixInterfaceLambdaMethod(ProgramClass programClass, ProgramMethod programMethod, LambdaExpression lambdaExpression) { // Change the access flags to package private to make the method // accessible from the lambda class. programMethod.accept(programClass, new MemberAccessSetter(0)); // If the method is not yet static, make it static // by updating its access flags / descriptor. if ((programMethod.getAccessFlags() & (AccessConstants.STATIC)) == 0) { programMethod.accept(programClass, new MemberAccessFlagSetter(AccessConstants.STATIC)); String newDescriptor = prependParameterToMethodDescriptor(lambdaExpression.invokedMethodDesc, ClassUtil.internalTypeFromClassType(programClass.getName())); programMethod.u2descriptorIndex = new ConstantPoolEditor(programClass).addUtf8Constant(newDescriptor); // Update the lambda expression accordingly. lambdaExpression.invokedMethodDesc = newDescriptor; lambdaExpression.invokedReferenceKind = MethodHandleConstant.REF_INVOKE_STATIC; } } private void addAccessorMethod(ProgramClass programClass, LambdaExpression lambdaExpression) { ClassBuilder classBuilder = new ClassBuilder(programClass, programClassPool, libraryClassPool); String className = programClass.getName(); // Create accessor method. String shortClassName = ClassUtil.externalShortClassName( ClassUtil.externalClassName(className)); String accessorMethodName = String.format("accessor$%s$lambda%d", shortClassName, lambdaExpression.bootstrapMethodIndex); String accessorMethodDescriptor = lambdaExpression.invokedMethodDesc; int accessFlags = lambdaExpression.referencedInvokedMethod.getAccessFlags(); logger.debug("LambdaExpressionConverter: creating accessor method [{}.{}{}]", className, accessorMethodName, accessorMethodDescriptor ); // Method reference to a constructor. if (lambdaExpression.invokedReferenceKind == MethodHandleConstant.REF_NEW_INVOKE_SPECIAL) { // Replace the return type of the accessor method -> change to created type. // Collect first all parameters. List invokedParameterTypes = new ArrayList(); int methodParameterSize = ClassUtil.internalMethodParameterSize(accessorMethodDescriptor); for (int i = 0; i < methodParameterSize; i++) { String invokedParameterType = ClassUtil.internalMethodParameterType(accessorMethodDescriptor, i); invokedParameterTypes.add(invokedParameterType); } String invokedClassType = ClassUtil.internalTypeFromClassName(lambdaExpression.invokedClassName); // Build new method descriptor with the updated return type. accessorMethodDescriptor = ClassUtil.internalMethodDescriptorFromInternalTypes(invokedClassType, invokedParameterTypes); } else if ((accessFlags & AccessConstants.STATIC) == 0) { accessorMethodDescriptor = prependParameterToMethodDescriptor(accessorMethodDescriptor, ClassUtil.internalTypeFromClassType(className)); } final String methodDescriptor = accessorMethodDescriptor; classBuilder.addMethod( AccessConstants.STATIC | AccessConstants.SYNTHETIC, accessorMethodName, accessorMethodDescriptor, 50, ____ -> { // If the lambda expression is a method reference to a constructor, // we need to create the object first. if (lambdaExpression.invokedReferenceKind == MethodHandleConstant.REF_NEW_INVOKE_SPECIAL) { ____.new_(lambdaExpression.invokedClassName) .dup(); } // Load the parameters next. completeInterfaceMethod(lambdaExpression, null, methodDescriptor, null, false, 0, ____); }); // Update the lambda expression to point to the created // accessor method instead. lambdaExpression.invokedClassName = programClass.getName(); lambdaExpression.invokedMethodName = accessorMethodName; lambdaExpression.invokedMethodDesc = accessorMethodDescriptor; lambdaExpression.invokedReferenceKind = MethodHandleConstant.REF_INVOKE_STATIC; lambdaExpression.referencedInvokedClass = programClass; lambdaExpression.referencedInvokedMethod = programClass.findMethod(accessorMethodName, accessorMethodDescriptor); } private void completeStatelessLambdaClass(ProgramClass lambdaClass, LambdaExpression lambdaExpression) { String lambdaClassType = ClassUtil.internalTypeFromClassName(lambdaClass.getName()); ClassBuilder classBuilder = new ClassBuilder(lambdaClass, programClassPool, libraryClassPool); // Add singleton field classBuilder.addField( AccessConstants.PUBLIC | AccessConstants.STATIC | AccessConstants.FINAL, LAMBDA_SINGLETON_FIELD_NAME, lambdaClassType); // Add the constructor. classBuilder.addMethod( AccessConstants.PUBLIC, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, 10, code -> code .aload_0() .invokespecial(ClassConstants.NAME_JAVA_LANG_OBJECT, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT) .return_()); // Add static initializer. classBuilder.addMethod( AccessConstants.STATIC, ClassConstants.METHOD_NAME_CLINIT, ClassConstants.METHOD_TYPE_CLINIT, 30, code -> code .new_(lambdaClass) .dup() .invokespecial(lambdaClass.getName(), ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT) .putstatic(lambdaClass.getName(), LAMBDA_SINGLETON_FIELD_NAME, lambdaClassType) .return_()); // If the lambda expression is serializable, create a readResolve method // to return the singleton field. if (lambdaExpression.isSerializable()) { classBuilder.addMethod( AccessConstants.PRIVATE, ClassConstants.METHOD_NAME_READ_RESOLVE, ClassConstants.METHOD_TYPE_READ_RESOLVE, 10, code -> code .getstatic(lambdaClass.getName(), LAMBDA_SINGLETON_FIELD_NAME, lambdaClassType) .areturn()); } logger.debug("LambdaExpressionConverter: creating interface method [{}.{}{}]", lambdaClass.getName(), lambdaExpression.interfaceMethod, lambdaExpression.interfaceMethodDescriptor ); // Add the interface method. classBuilder.addMethod( AccessConstants.PUBLIC, lambdaExpression.interfaceMethod, lambdaExpression.interfaceMethodDescriptor, 50, ____ -> { if (lambdaExpression.invokedReferenceKind == MethodHandleConstant.REF_NEW_INVOKE_SPECIAL) { ____.new_(lambdaExpression.invokedClassName) .dup(); // Convert the remaining parameters if they are present. completeInterfaceMethod(lambdaExpression, null, lambdaExpression.interfaceMethodDescriptor, lambdaExpression.invokedMethodDesc, false, 1, ____); } else { boolean isInvokeVirtualOrInterface = lambdaExpression.invokedReferenceKind == MethodHandleConstant.REF_INVOKE_VIRTUAL || lambdaExpression.invokedReferenceKind == MethodHandleConstant.REF_INVOKE_INTERFACE; // Convert the remaining parameters if they are present. completeInterfaceMethod(lambdaExpression, null, lambdaExpression.interfaceMethodDescriptor, lambdaExpression.invokedMethodDesc, isInvokeVirtualOrInterface, 1, ____); } }); } private void completeCapturingLambdaClass(ProgramClass lambdaClass, LambdaExpression lambdaExpression) { ClassBuilder classBuilder = new ClassBuilder(lambdaClass, programClassPool, libraryClassPool); String lambdaClassName = lambdaClass.getName(); // Add the constructor. String ctorDescriptor = lambdaExpression.getConstructorDescriptor(); logger.debug("LambdaExpressionConverter: creating constructor [{}.{}{}]", lambdaClass, ClassConstants.METHOD_NAME_INIT, ctorDescriptor ); classBuilder.addMethod( AccessConstants.PUBLIC, ClassConstants.METHOD_NAME_INIT, ctorDescriptor, 50, ____ -> { ____.aload_0() .invokespecial(ClassConstants.NAME_JAVA_LANG_OBJECT, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT); InternalTypeEnumeration ctorTypeEnumeration = new InternalTypeEnumeration(ctorDescriptor); int ctorArgIndex = 0; int ctorVariableIndex = 1; while (ctorTypeEnumeration.hasMoreTypes()) { String fieldName = "arg$" + ctorArgIndex++; String fieldType = ctorTypeEnumeration.nextType(); ____.aload_0(); ____.load(ctorVariableIndex, fieldType); ____.putfield(lambdaClassName, fieldName, fieldType); ctorVariableIndex += ClassUtil.internalTypeSize(fieldType); } ____.return_(); }); // Add the fields. InternalTypeEnumeration typeEnumeration = new InternalTypeEnumeration(ctorDescriptor); int argIndex = 0; while (typeEnumeration.hasMoreTypes()) { String type = typeEnumeration.nextType(); String fieldName = "arg$" + argIndex++; logger.debug("LambdaExpressionConverter: creating field [{}.{} {}]", lambdaClass, fieldName, type ); classBuilder.addField(AccessConstants.PRIVATE | AccessConstants.FINAL, fieldName, type); } logger.debug("LambdaExpressionConverter: creating interface method [{}.{}{}]", lambdaClassName, lambdaExpression.interfaceMethod, lambdaExpression.interfaceMethodDescriptor ); // Add the interface method implementation. classBuilder.addMethod( AccessConstants.PUBLIC, lambdaExpression.interfaceMethod, lambdaExpression.interfaceMethodDescriptor, 50, ____ -> { boolean isInvokeVirtualOrInterface = lambdaExpression.invokedReferenceKind == MethodHandleConstant.REF_INVOKE_VIRTUAL || lambdaExpression.invokedReferenceKind == MethodHandleConstant.REF_INVOKE_INTERFACE; // Load the instance fields and the remaining parameters. completeInterfaceMethod(lambdaExpression, ctorDescriptor, lambdaExpression.interfaceMethodDescriptor, lambdaExpression.invokedMethodDesc, isInvokeVirtualOrInterface, 1, ____); }); } private void completeInterfaceMethod(LambdaExpression lambdaExpression, String fieldTypes, String methodDescriptor, String invokedMethodDescriptor, boolean isInvokeVirtualOrInterface, int parameterIndex, CompactCodeAttributeComposer ____) { InternalTypeEnumeration typeEnumeration = new InternalTypeEnumeration(methodDescriptor); InternalTypeEnumeration invokedTypeEnumeration = invokedMethodDescriptor == null ? null : new InternalTypeEnumeration(invokedMethodDescriptor); // Get the instance fields. if (fieldTypes != null) { String lambdaClassName = ____.getTargetClass().getName(); InternalTypeEnumeration fieldTypeEnumeration = new InternalTypeEnumeration(fieldTypes); int fieldIndex = 0; while (fieldTypeEnumeration.hasMoreTypes()) { String fieldName = "arg$" + fieldIndex; String fieldType = fieldTypeEnumeration.nextType(); ____.aload_0() .getfield(lambdaClassName, fieldName, fieldType); if (!isInvokeVirtualOrInterface || fieldIndex > 0) { convertToTargetType(fieldType, invokedTypeEnumeration.nextType(), ____); } fieldIndex++; } } // If we invoke a method on an object, we need to cast it to the invoked type. else if (isInvokeVirtualOrInterface) { String type = typeEnumeration.nextType(); String invokedType = ClassUtil.internalTypeFromClassName(lambdaExpression.invokedClassName); ____.load(parameterIndex, type); parameterIndex += ClassUtil.internalTypeSize(type); convertToTargetType(type, invokedType, ____); } // Load the remaining arguments. while (typeEnumeration.hasMoreTypes()) { String type = typeEnumeration.nextType(); String invokedType = invokedTypeEnumeration != null ? invokedTypeEnumeration.nextType() : null; ____.load(parameterIndex, type); parameterIndex += ClassUtil.internalTypeSize(type); if (invokedType != null) { convertToTargetType(type, invokedType, ____); } } // Invoke the method. switch (lambdaExpression.invokedReferenceKind) { case MethodHandleConstant.REF_INVOKE_STATIC: if (lambdaExpression.invokesStaticInterfaceMethod()) { ____.invokestatic_interface(lambdaExpression.invokedClassName, lambdaExpression.invokedMethodName, lambdaExpression.invokedMethodDesc, lambdaExpression.referencedInvokedClass, lambdaExpression.referencedInvokedMethod); } else { ____.invokestatic(lambdaExpression.invokedClassName, lambdaExpression.invokedMethodName, lambdaExpression.invokedMethodDesc, lambdaExpression.referencedInvokedClass, lambdaExpression.referencedInvokedMethod); } break; case MethodHandleConstant.REF_INVOKE_VIRTUAL: ____.invokevirtual(lambdaExpression.invokedClassName, lambdaExpression.invokedMethodName, lambdaExpression.invokedMethodDesc, lambdaExpression.referencedInvokedClass, lambdaExpression.referencedInvokedMethod); break; case MethodHandleConstant.REF_INVOKE_INTERFACE: ____.invokeinterface(lambdaExpression.invokedClassName, lambdaExpression.invokedMethodName, lambdaExpression.invokedMethodDesc, lambdaExpression.referencedInvokedClass, lambdaExpression.referencedInvokedMethod); break; case MethodHandleConstant.REF_NEW_INVOKE_SPECIAL: case MethodHandleConstant.REF_INVOKE_SPECIAL: ____.invokespecial(lambdaExpression.invokedClassName, lambdaExpression.invokedMethodName, lambdaExpression.invokedMethodDesc, lambdaExpression.referencedInvokedClass, lambdaExpression.referencedInvokedMethod); break; } // Cast the return type. String methodReturnType = typeEnumeration.returnType(); if (invokedTypeEnumeration != null) { convertToTargetType(invokedTypeEnumeration.returnType(), methodReturnType, ____); } ____.return_(methodReturnType); } private void addBridgeMethods(ProgramClass lambdaClass, LambdaExpression lambdaExpression) { ClassBuilder classBuilder = new ClassBuilder(lambdaClass, programClassPool, libraryClassPool); String methodName = lambdaExpression.interfaceMethod; for (String bridgeMethodDescriptor : lambdaExpression.bridgeMethodDescriptors) { Method method = lambdaClass.findMethod(methodName, bridgeMethodDescriptor); if (method == null) { logger.debug("LambdaExpressionConverter: adding bridge method [{}.{}{}]", lambdaClass.getName(), methodName, bridgeMethodDescriptor ); classBuilder.addMethod( AccessConstants.PUBLIC | AccessConstants.SYNTHETIC | AccessConstants.BRIDGE, methodName, bridgeMethodDescriptor, 50, ____ -> { ____.aload_0(); InternalTypeEnumeration interfaceTypeEnumeration = new InternalTypeEnumeration(lambdaExpression.interfaceMethodDescriptor); InternalTypeEnumeration bridgeTypeEnumeration = new InternalTypeEnumeration(bridgeMethodDescriptor); int variableIndex = 1; while (bridgeTypeEnumeration.hasMoreTypes()) { String type = bridgeTypeEnumeration.nextType(); String interfaceType = interfaceTypeEnumeration.nextType(); ____.load(variableIndex, type); variableIndex += ClassUtil.internalTypeSize(type); convertToTargetType(type, interfaceType, ____); } ____.invokevirtual(lambdaClass.getName(), lambdaExpression.interfaceMethod, lambdaExpression.interfaceMethodDescriptor); String methodReturnType = bridgeTypeEnumeration.returnType(); convertToTargetType(interfaceTypeEnumeration.returnType(), methodReturnType, ____); ____.return_(methodReturnType); }); } } } private static String prependParameterToMethodDescriptor(String methodDescriptor, String type) { StringBuilder methodDescBuilder = new StringBuilder(); methodDescBuilder.append('('); methodDescBuilder.append(type); InternalTypeEnumeration typeEnumeration = new InternalTypeEnumeration(methodDescriptor); while (typeEnumeration.hasMoreTypes()) { methodDescBuilder.append(typeEnumeration.nextType()); } methodDescBuilder.append(')'); methodDescBuilder.append(typeEnumeration.returnType()); return methodDescBuilder.toString(); } /** * Adds the required instructions to the provided CodeAttributeComposer * to convert the current value on the stack to the given targetType. */ private static void convertToTargetType(String sourceType, String targetType, CompactCodeAttributeComposer composer) { if (ClassUtil.isInternalPrimitiveType(sourceType) && !ClassUtil.isInternalPrimitiveType(targetType)) { // Perform auto-boxing. switch (sourceType.charAt(0)) { case TypeConstants.INT: composer.invokestatic(ClassConstants.NAME_JAVA_LANG_INTEGER, "valueOf", "(I)Ljava/lang/Integer;"); break; case TypeConstants.BYTE: composer.invokestatic(ClassConstants.NAME_JAVA_LANG_BYTE, "valueOf", "(B)Ljava/lang/Byte;"); break; case TypeConstants.CHAR: composer.invokestatic(ClassConstants.NAME_JAVA_LANG_CHARACTER, "valueOf", "(C)Ljava/lang/Character;"); break; case TypeConstants.SHORT: composer.invokestatic(ClassConstants.NAME_JAVA_LANG_SHORT, "valueOf", "(S)Ljava/lang/Short;"); break; case TypeConstants.BOOLEAN: composer.invokestatic(ClassConstants.NAME_JAVA_LANG_BOOLEAN, "valueOf", "(Z)Ljava/lang/Boolean;"); break; case TypeConstants.LONG: composer.invokestatic(ClassConstants.NAME_JAVA_LANG_LONG, "valueOf", "(J)Ljava/lang/Long;"); break; case TypeConstants.FLOAT: composer.invokestatic(ClassConstants.NAME_JAVA_LANG_FLOAT, "valueOf", "(F)Ljava/lang/Float;"); break; case TypeConstants.DOUBLE: composer.invokestatic(ClassConstants.NAME_JAVA_LANG_DOUBLE, "valueOf", "(D)Ljava/lang/Double;"); break; } } else if (!ClassUtil.isInternalPrimitiveType(sourceType) && ClassUtil.isInternalPrimitiveType(targetType)) { boolean castRequired = sourceType.equals(ClassConstants.TYPE_JAVA_LANG_OBJECT); // Perform auto-unboxing. switch (targetType.charAt(0)) { case TypeConstants.INT: if (castRequired) { composer.checkcast("java/lang/Number"); } composer.invokevirtual("java/lang/Number", "intValue", "()I"); break; case TypeConstants.BYTE: if (castRequired) { composer.checkcast(ClassConstants.NAME_JAVA_LANG_BYTE); } composer.invokevirtual(ClassConstants.NAME_JAVA_LANG_BYTE, "byteValue", "()B"); break; case TypeConstants.CHAR: if (castRequired) { composer.checkcast(ClassConstants.NAME_JAVA_LANG_CHARACTER); } composer.invokevirtual(ClassConstants.NAME_JAVA_LANG_CHARACTER, "charValue", "()C"); break; case TypeConstants.SHORT: if (castRequired) { composer.checkcast(ClassConstants.NAME_JAVA_LANG_SHORT); } composer.invokevirtual(ClassConstants.NAME_JAVA_LANG_SHORT, "shortValue", "()S"); break; case TypeConstants.BOOLEAN: if (castRequired) { composer.checkcast(ClassConstants.NAME_JAVA_LANG_BOOLEAN); } composer.invokevirtual(ClassConstants.NAME_JAVA_LANG_BOOLEAN, "booleanValue", "()Z"); break; case TypeConstants.LONG: if (castRequired) { composer.checkcast("java/lang/Number"); } composer.invokevirtual("java/lang/Number", "longValue", "()J"); break; case TypeConstants.FLOAT: if (castRequired) { composer.checkcast("java/lang/Number"); } composer.invokevirtual("java/lang/Number", "floatValue", "()F"); break; case TypeConstants.DOUBLE: if (castRequired) { composer.checkcast("java/lang/Number"); } composer.invokevirtual("java/lang/Number", "doubleValue", "()D"); break; } } else if (ClassUtil.isInternalClassType(sourceType) && (ClassUtil.isInternalClassType(targetType) || ClassUtil.isInternalArrayType(targetType)) && !sourceType.equals(targetType) && // No need to cast to java/lang/Object. !ClassConstants.TYPE_JAVA_LANG_OBJECT.equals(targetType)) { // Cast to target type. composer.checkcast(ClassUtil.internalClassTypeFromType(targetType)); } } } ================================================ FILE: base/src/main/java/proguard/backport/StaticInterfaceMethodConverter.java ================================================ /* * 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.backport; import proguard.classfile.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.*; import proguard.classfile.editor.*; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; import java.util.*; /** * This ClassVisitor moves all static interface methods in the visited * interfaces to a separate util class and updates all invocations in * the program class pool. * * @author Thomas Neidhart */ public class StaticInterfaceMethodConverter implements ClassVisitor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final ExtraDataEntryNameMap extraDataEntryNameMap; private final ClassVisitor modifiedClassVisitor; private final MemberVisitor extraMemberVisitor; public StaticInterfaceMethodConverter(ClassPool programClassPool, ClassPool libraryClassPool, ExtraDataEntryNameMap extraDataEntryNameMap, ClassVisitor modifiedClassVisitor, MemberVisitor extraMemberVisitor) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.extraDataEntryNameMap = extraDataEntryNameMap; this.modifiedClassVisitor = modifiedClassVisitor; this.extraMemberVisitor = extraMemberVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Collect all static methods of the interface class. Set staticMethods = new HashSet<>(); programClass.accept( new AllMethodVisitor( new MemberAccessFilter(AccessConstants.STATIC, 0, new InitializerMethodFilter(null, new MemberCollector(false, true, true, staticMethods))))); if (!staticMethods.isEmpty()) { // Create a new utility class. ProgramClass utilityClass = new ClassBuilder( VersionConstants.CLASS_VERSION_1_2, AccessConstants.PUBLIC | AccessConstants.SYNTHETIC, programClass.getName() + "$$Util", ClassConstants.NAME_JAVA_LANG_OBJECT) // Add a private constructor. .addMethod( AccessConstants.PRIVATE, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, 10, code -> code .aload_0() .invokespecial(ClassConstants.NAME_JAVA_LANG_OBJECT, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT) .return_()) .getProgramClass(); // Copy all static interface methods to the utility class. MemberVisitor memberAdder = new MemberAdder(utilityClass); if (extraMemberVisitor != null) { memberAdder = new MultiMemberVisitor( memberAdder, extraMemberVisitor ); } MemberRemover memberRemover = new MemberRemover(); programClass.accept( new AllMethodVisitor( new MemberAccessFilter(AccessConstants.STATIC, 0, new InitializerMethodFilter(null, new MultiMemberVisitor( // Add the method to the utility class. memberAdder, // Mark the method for removal from the // interface class. memberRemover ) )))); // Add the utility class to the program class pool // and the injected class name map. programClassPool.addClass(utilityClass); extraDataEntryNameMap.addExtraClassToClass(programClass, utilityClass); // Change all invokestatic invocations of the static interface // methods to use the utility class instead. replaceInstructions(programClass, utilityClass, staticMethods); // Initialize the hierarchy and references of the utility class. utilityClass.accept( new MultiClassVisitor( new ClassSuperHierarchyInitializer(programClassPool, libraryClassPool), new ClassReferenceInitializer(programClassPool, libraryClassPool) )); // Remove the static methods from the interface class and // shrink the constant pool of unused constants. programClass.accept( new MultiClassVisitor( memberRemover, new ConstantPoolShrinker() )); } } // Small utility methods. /** * Replaces all static invocations of the given methods in the given * interface class by invocations of copies of these methods in the * given utility class. */ private void replaceInstructions(ProgramClass interfaceClass, ProgramClass utilityClass, Set staticMethods) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); Instruction[][][] instructions = new Instruction[staticMethods.size()][][]; int index = 0; for (String staticMethod : staticMethods) { String[] splitArray = staticMethod.split("\\."); String methodName = splitArray[0]; String methodDesc = splitArray[1]; Instruction[][] replacement = new Instruction[][] { ____.invokestatic_interface(interfaceClass.getName(), methodName, methodDesc).__(), ____.invokestatic(utilityClass.getName(), methodName, methodDesc).__(), }; instructions[index++] = replacement; } CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); InstructionVisitor updatedClassVisitor = new InstructionToAttributeVisitor( new AttributeToClassVisitor( modifiedClassVisitor)); programClassPool.classesAccept( new MyReferencedClassFilter(interfaceClass, new AllMethodVisitor( new AllAttributeVisitor( new PeepholeEditor(codeAttributeEditor, new InstructionSequencesReplacer(____.constants(), instructions, null, codeAttributeEditor, updatedClassVisitor)))))); } /** * This ClassVisitor delegates its visits to classes that * reference a given class via any RefConstant. */ private static class MyReferencedClassFilter implements ClassVisitor, ConstantVisitor { private final Clazz referencedClass; private final ClassVisitor classVisitor; private boolean referenceClassFound; public MyReferencedClassFilter(Clazz referencedClass, ClassVisitor classVisitor) { this.referencedClass = referencedClass; this.classVisitor = classVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { referenceClassFound = false; programClass.constantPoolEntriesAccept(this); if (referenceClassFound) { programClass.accept(classVisitor); } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { if (refConstant.referencedClass == referencedClass) { referenceClassFound = true; } } } } ================================================ FILE: base/src/main/java/proguard/backport/StreamSupportConverter.java ================================================ /* * 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.backport; import proguard.classfile.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; /** * This ClassVisitor will replace any occurrence of stream related methods / types * that have been introduced in Java 8 to the streamsupport library. * * @author Thomas Neidhart */ public class StreamSupportConverter extends AbstractAPIConverter { /** * Create a new StreamSupportConverter instance. */ public StreamSupportConverter(ClassPool programClassPool, ClassPool libraryClassPool, WarningPrinter warningPrinter, ClassVisitor modifiedClassVisitor, InstructionVisitor extraInstructionVisitor) { super(programClassPool, libraryClassPool, warningPrinter, modifiedClassVisitor, extraInstructionVisitor); TypeReplacement[] typeReplacements = new TypeReplacement[] { // j.u.stream package has been added in Java 8 replace("java/util/stream/**", "java8/util/stream/<1>"), // j.u.function package has been added in Java 8 replace("java/util/function/**", "java8/util/function/<1>"), // j.u classes / interfaces that have been added in Java 8 replace("java/util/DoubleSummaryStatistics", "java8/util/DoubleSummaryStatistics"), replace("java/util/IntSummaryStatistics", "java8/util/IntSummaryStatistics"), replace("java/util/LongSummaryStatistics", "java8/util/LongSummaryStatistics"), replace("java/util/PrimitiveIterator**", "java8/util/PrimitiveIterator<1>"), replace("java/util/Optional**", "java8/util/Optional<1>"), replace("java/util/Spliterator**", "java8/util/Spliterator<1>"), replace("java/util/SplittableRandom", "java8/util/SplittableRandom"), replace("java/util/StringJoiner", "java8/util/StringJoiner"), // j.u.c classes / exceptions that have been added in Java 8 or being updated in Java 8 replace("java/util/concurrent/CompletionException", "java8/util/concurrent/CompletionException"), replace("java/util/concurrent/CountedCompleter", "java8/util/concurrent/CountedCompleter"), replace("java/util/concurrent/ForkJoinPool", "java8/util/concurrent/ForkJoinPool"), replace("java/util/concurrent/ForkJoinTask", "java8/util/concurrent/ForkJoinTask"), replace("java/util/concurrent/ForkJoinWorkerTask", "java8/util/concurrent/ForkJoinWorkerTask"), replace("java/util/concurrent/Phaser", "java8/util/concurrent/Phaser"), replace("java/util/concurrent/RecursiveAction", "java8/util/concurrent/RecursiveAction"), replace("java/util/concurrent/RecursiveTask", "java8/util/concurrent/RecursiveTask"), replace("java/util/concurrent/ThreadLocalRandom", "java8/util/concurrent/ThreadLocalRandom"), // j.l classes / annotations that have been added in Java 8 replace("java/lang/FunctionalInterface", "java8/lang/FunctionalInterface"), }; MethodReplacement[] methodReplacements = new MethodReplacement[] { // default methods in j.u.Collection replace("java/util/Collection", "stream", "()Ljava/util/stream/Stream;", "java8/util/stream/StreamSupport", "stream", "(Ljava/util/Collection;)Ljava8/util/stream/Stream;"), replace("java/util/Collection", "parallelStream", "()Ljava/util/stream/Stream;", "java8/util/stream/StreamSupport", "parallelStream", "(Ljava/util/Collection;)Ljava8/util/stream/Stream;"), replace("java/util/Collection", "spliterator", "()Ljava/util/Spliterator;", "java8/util/Spliterators", "spliterator", "(Ljava/util/Collection;)Ljava8/util/Spliterator;"), replace("java/util/Collection", "removeIf", "(Ljava/util/function/Predicate;)Z", "java8/lang/Iterables", "removeIf", "(Ljava/lang/Iterable;Ljava8/util/function/Predicate;)Z"), // default methods in j.l.Iterable replace("java/lang/Iterable", "forEach", "(Ljava/util/function/Consumer;)V", "java8/lang/Iterables", "forEach", "(Ljava/lang/Iterable;Ljava8/util/function/Consumer;)V"), replace("java/lang/Iterable", "spliterator", "()Ljava/util/stream/Stream;", "java8/lang/Iterables", "spliterator", "(Ljava/lang/Iterable;)Ljava8/util/stream/Stream;"), // remaining default methods in j.u.List replace("java/util/List", "", "**", "java8/util/Lists", "<1>", "<1>"), // default methods in j.u.Map replace("java/util/Map", "", "**", "java8/util/Maps", "<1>", "<1>"), // static methods in j.u.Map$Entry replace("java/util/Map$Entry", "", "**", "java8/util/Maps", "<1>", "<1>"), // default and static methods in j.u.Comparator replace("java/util/Comparator", "", "**", "java8/util/Comparators", "<1>", "<1>"), replace("java/util/Comparator", "", "**", "java8/util/Comparators", "<1>", "<1>"), // all methods of new classes in j.u. replace("java/util/DoubleSummaryStatistics", "**", "**", "java8/util/DoubleSummaryStatistics", "<1>", "<1>"), replace("java/util/IntSummaryStatistics", "**", "**", "java8/util/IntSummaryStatistics", "<1>", "<1>"), replace("java/util/LongSummaryStatistics", "**", "**", "java8/util/LongSummaryStatistics", "<1>", "<1>"), replace("java/util/PrimitiveIterator**", "**", "**", "java8/util/PrimitiveIterator<1>", "<1>", "<1>"), replace("java/util/Optional**", "**", "**", "java8/util/Optional<1>", "<1>", "<1>"), replace("java/util/Spliterator**", "**", "**", "java8/util/Spliterator<1>", "<1>", "<1>"), replace("java/util/SplittableRandom", "**", "**", "java8/util/SplittableRandom", "<1>", "<1>"), replace("java/util/StringJoiner", "**", "**", "java8/util/StringJoiner", "<1>", "<1>"), // default and static methods in new interfaces. replace("java/util/function/BiConsumer", "", "**", "java8/util/function/BiConsumers", "<1>", "<1>"), replace("java/util/function/BiFunction", "", "**", "java8/util/function/BiFunctions", "<1>", "<1>"), replace("java/util/function/BinaryOperator", "", "**", "java8/util/function/BinaryOperators", "<1>", "<1>"), replace("java/util/function/BiPredicate", "", "**", "java8/util/function/BiPredicates", "<1>", "<1>"), replace("java/util/function/Consumer", "", "**", "java8/util/function/Consumers", "<1>", "<1>"), replace("java/util/function/DoubleConsumer", "", "**", "java8/util/function/DoubleConsumers", "<1>", "<1>"), replace("java/util/function/DoublePredicate", "", "**", "java8/util/function/DoublePredicates", "<1>", "<1>"), replace("java/util/function/DoubleUnaryOperator", "", "**", "java8/util/function/DoubleUnaryOperators", "<1>", "<1>"), replace("java/util/function/DoubleUnaryOperator", "", "**", "java8/util/function/DoubleUnaryOperators", "<1>", "<1>"), replace("java/util/function/Function", "", "**", "java8/util/function/Functions", "<1>", "<1>"), replace("java/util/function/Function", "", "**", "java8/util/function/Functions", "<1>", "<1>"), replace("java/util/function/IntConsumer", "", "**", "java8/util/function/IntConsumers", "<1>", "<1>"), replace("java/util/function/IntPredicate", "", "**", "java8/util/function/IntPredicates", "<1>", "<1>"), replace("java/util/function/IntUnaryOperator", "", "**", "java8/util/function/IntUnaryOperators", "<1>", "<1>"), replace("java/util/function/IntUnaryOperator", "", "**", "java8/util/function/IntUnaryOperators", "<1>", "<1>"), replace("java/util/function/LongConsumer", "", "**", "java8/util/function/LongConsumers", "<1>", "<1>"), replace("java/util/function/LongPredicate", "", "**", "java8/util/function/LongPredicates", "<1>", "<1>"), replace("java/util/function/LongUnaryOperator", "", "**", "java8/util/function/LongUnaryOperators", "<1>", "<1>"), replace("java/util/function/LongUnaryOperator", "", "**", "java8/util/function/LongUnaryOperators", "<1>", "<1>"), replace("java/util/function/Predicate", "", "**", "java8/util/function/Predicates", "<1>", "<1>"), replace("java/util/function/Predicate", "", "**", "java8/util/function/Predicates", "<1>", "<1>"), replace("java/util/function/UnaryOperator", "", "**", "java8/util/function/UnaryOperators", "<1>", "<1>"), // static methods in new interfaces. replace("java/util/stream/DoubleStream", "", "**", "java8/util/stream/DoubleStreams", "<1>", "<1>"), replace("java/util/stream/IntStream", "", "**", "java8/util/stream/IntStreams", "<1>", "<1>"), replace("java/util/stream/LongStream", "", "**", "java8/util/stream/LongStreams", "<1>", "<1>"), replace("java/util/stream/Stream", "", "**", "java8/util/stream/RefStreams", "<1>", "<1>"), replace("java/util/stream/Collector", "", "**", "java8/util/stream/Collectors", "<1>", "<1>"), // remaining methods in new classes. replace("java/util/stream/**", "**", "**", "java8/util/stream/<1>", "<1>", "<1>"), replace("java/util/function/**", "**", "**", "java8/util/function/<1>", "<1>", "<1>"), // default methods in Iterator. replace("java/util/Iterator", "forEachRemaining", "(Ljava/util/function/Consumer;)V", "java8/util/Iterators", "forEachRemaining", "(Ljava/lang/Iterable;Ljava8/util/function/Consumer;)V"), // new methods in j.u.c. replace("java/util/concurrent/ConcurrentMap", "", "**", "java8/util/concurrent/ConcurrentMaps", "<1>", "<1>"), replace("java/util/concurrent/CompletionException", "**", "**", "java8/util/concurrent/CompletionException", "<1>", "<1>"), replace("java/util/concurrent/CountedCompleter", "**", "**", "java8/util/concurrent/CountedCompleter", "<1>", "<1>"), replace("java/util/concurrent/ForkJoinPool", "**", "**", "java8/util/concurrent/ForkJoinPool", "<1>", "<1>"), replace("java/util/concurrent/ForkJoinTask", "**", "**", "java8/util/concurrent/ForkJoinTask", "<1>", "<1>"), replace("java/util/concurrent/ForkJoinWorkerTask", "**", "**", "java8/util/concurrent/ForkJoinWorkerTask", "<1>", "<1>"), replace("java/util/concurrent/Phaser", "**", "**", "java8/util/concurrent/Phaser", "<1>", "<1>"), replace("java/util/concurrent/RecursiveAction", "**", "**", "java8/util/concurrent/RecursiveAction", "<1>", "<1>"), replace("java/util/concurrent/RecursiveTask", "**", "**", "java8/util/concurrent/RecursiveTask", "<1>", "<1>"), replace("java/util/concurrent/ForkJoinPool", "**", "**", "java8/util/concurrent/ForkJoinPool", "<1>", "<1>"), // static methods replace("java/util/concurrent/ThreadLocalRandom", "ints", "**", "java8/util/concurrent/ThreadLocalRandom", "ints", "<1>"), replace("java/util/concurrent/ThreadLocalRandom", "longs", "**", "java8/util/concurrent/ThreadLocalRandom", "longs", "<1>"), replace("java/util/concurrent/ThreadLocalRandom", "doubles", "**", "java8/util/concurrent/ThreadLocalRandom", "doubles", "<1>"), // remaining replace("java/util/concurrent/ThreadLocalRandom", "**", "**", "java8/util/concurrent/ThreadLocalRandom", "<1>", "<1>"), // new methods in j.u.Arrays. replace("java/util/Arrays", "spliterator", "**", "java8/util/J8Arrays", "spliterator", "<1>"), replace("java/util/Arrays", "stream", "**", "java8/util/J8Arrays", "stream", "<1>"), replace("java/util/Arrays", "parallel**", "**", "java8/util/J8Arrays", "<1>", "<1>"), replace("java/util/Arrays", "set**", "**", "java8/util/J8Arrays", "<1>", "<1>"), // new methods in j.l.Integer. replace("java/lang/Integer", "min", "**", "java8/lang/Integers", "min", "<1>"), replace("java/lang/Integer", "max", "**", "java8/lang/Integers", "max", "<1>"), replace("java/lang/Integer", "sum", "**", "java8/lang/Integers", "sum", "<1>"), replace("java/lang/Integer", "compare", "**", "java8/lang/Integers", "compare", "<1>"), replace("java/lang/Integer", "compareUnsigned", "**", "java8/lang/Integers", "compareUnsigned", "<1>"), replace("java/lang/Integer", "remainderUnsigned", "**", "java8/lang/Integers", "remainderUnsigned", "<1>"), replace("java/lang/Integer", "divideUnsigned", "**", "java8/lang/Integers", "divideUnsigned", "<1>"), replace("java/lang/Integer", "toUnsignedLong", "**", "java8/lang/Integers", "toUnsignedLong", "<1>"), replace("java/lang/Integer", "hashCode", "(I)I", "java8/lang/Integers", "hashCode", "(I)I"), // new methods in j.l.Long. replace("java/lang/Long", "min", "**", "java8/lang/Longs", "min", "<1>"), replace("java/lang/Long", "max", "**", "java8/lang/Longs", "max", "<1>"), replace("java/lang/Long", "sum", "**", "java8/lang/Longs", "sum", "<1>"), replace("java/lang/Long", "compare", "**", "java8/lang/Longs", "compare", "<1>"), replace("java/lang/Long", "compareUnsigned", "**", "java8/lang/Longs", "compareUnsigned", "<1>"), replace("java/lang/Long", "remainderUnsigned", "**", "java8/lang/Longs", "remainderUnsigned", "<1>"), replace("java/lang/Long", "divideUnsigned", "**", "java8/lang/Longs", "divideUnsigned", "<1>"), replace("java/lang/Long", "toUnsignedBigInteger", "**", "java8/lang/Longs", "toUnsignedBigInteger", "<1>"), replace("java/lang/Long", "hashCode", "(J)I", "java8/lang/Longs", "hashCode", "(J)I"), // new methods in j.l.Double. replace("java/lang/Double", "min", "**", "java8/lang/Doubles", "min", "<1>"), replace("java/lang/Double", "max", "**", "java8/lang/Doubles", "max", "<1>"), replace("java/lang/Double", "sum", "**", "java8/lang/Doubles", "sum", "<1>"), replace("java/lang/Double", "isFinite", "**", "java8/lang/Doubles", "isFinite", "<1>"), replace("java/lang/Double", "hashCode", "(D)I", "java8/lang/Doubles", "hashCode", "(D)I"), // missing replacements for methods added to j.u.Random. missing("java/util/Random", "ints", "**"), missing("java/util/Random", "longs", "**"), missing("java/util/Random", "doubles", "**") }; setTypeReplacements(typeReplacements); setMethodReplacements(methodReplacements); } } ================================================ FILE: base/src/main/java/proguard/backport/StringConcatenationConverter.java ================================================ /* * 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.backport; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import java.util.*; /** * This InstructionVisitor converts all indy String Concatenations in the visited * classes to StringBuilder-append chains. * * @author Tim Van Den Broecke */ public class StringConcatenationConverter implements InstructionVisitor, // Implementation interfaces. AttributeVisitor, BootstrapMethodInfoVisitor, ConstantVisitor { private static final int MAXIMUM_BOOLEAN_AS_STRING_LENGTH = 5; // "false" private static final int MAXIMUM_CHAR_AS_STRING_LENGTH = 1; // "any char" private static final int MAXIMUM_BYTE_AS_STRING_LENGTH = 3; // "255" private static final int MAXIMUM_SHORT_AS_STRING_LENGTH = 6; // "-32768" private static final int MAXIMUM_INT_AS_STRING_LENGTH = 11; // "-2147483648" private static final int MAXIMUM_LONG_AS_STRING_LENGTH = 20; // "-9223372036854775808" private static final int MAXIMUM_FLOAT_AS_STRING_LENGTH = 13; // "-3.4028235E38" private static final int MAXIMUM_DOUBLE_AS_STRING_LENGTH = 23; // "-1.7976931348623157E308" private static final int MAXIMUM_AT_HASHCODE_LENGTH = MAXIMUM_CHAR_AS_STRING_LENGTH + MAXIMUM_INT_AS_STRING_LENGTH; private static final int DEFAULT_STRINGBUILDER_INIT_SIZE = 16; // Constants as per specification private static final char C_VARIABLE_ARGUMENT = '\u0001'; private static final char C_CONSTANT_ARGUMENT = '\u0002'; private final InstructionVisitor extraInstructionVisitor; private final CodeAttributeEditor codeAttributeEditor; private InstructionSequenceBuilder appendChainComposer; private int estimatedStringLength; private int referencedBootstrapMethodIndex; private String concatenationRecipe; private int[] concatenationConstants; public StringConcatenationConverter(InstructionVisitor extraInstructionVisitor, CodeAttributeEditor codeAttributeEditor) { this.extraInstructionVisitor = extraInstructionVisitor; this.codeAttributeEditor = codeAttributeEditor; } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_INVOKEDYNAMIC) { ProgramClass programClass = (ProgramClass) clazz; InvokeDynamicConstant invokeDynamicConstant = (InvokeDynamicConstant) programClass.getConstant(constantInstruction.constantIndex); // Remember the referenced bootstrap method index and extract the recipe from it. referencedBootstrapMethodIndex = invokeDynamicConstant.getBootstrapMethodAttributeIndex(); concatenationRecipe = null; concatenationConstants = null; programClass.attributesAccept(this); if (concatenationRecipe != null) { //if (isMakeConcatWithConstants(invokeDynamicConstant.getName(programClass))) String descriptor = invokeDynamicConstant.getType(programClass); InstructionSequenceBuilder mainReplacementComposer = new InstructionSequenceBuilder(programClass); appendChainComposer = new InstructionSequenceBuilder(programClass); estimatedStringLength = 0; // Collect the argument types. InternalTypeEnumeration typeEnumeration = new InternalTypeEnumeration(descriptor); List types = new ArrayList(); while (typeEnumeration.hasMoreTypes()) { types.add(typeEnumeration.nextType()); } // Store the correct number of stack values in reverse // order in local variables int variableIndex = codeAttribute.u2maxLocals; ListIterator typeIterator = types.listIterator(types.size()); while (typeIterator.hasPrevious()) { String type = typeIterator.previous(); mainReplacementComposer.store(variableIndex, type); variableIndex += ClassUtil.internalTypeSize(type); } // Loop over the recipe. // Push the local variables one by one, insert // constants where necessary and create and append // instruction chain. typeIterator = types.listIterator(); for (int argIndex = 0, constantCounter = 0; argIndex < concatenationRecipe.length(); argIndex++) { switch (concatenationRecipe.charAt(argIndex)) { case C_VARIABLE_ARGUMENT: String type = typeIterator.next(); estimatedStringLength += typicalStringLengthFromType(type); int variableSize = ClassUtil.internalTypeSize(type); variableIndex -= variableSize; appendChainComposer.load(variableIndex, type) .invokevirtual(ClassConstants.NAME_JAVA_LANG_STRING_BUILDER, ClassConstants.METHOD_NAME_APPEND, appendDescriptorFromInternalType(type)); break; case C_CONSTANT_ARGUMENT: int constantIndex = concatenationConstants[constantCounter++]; appendChainComposer.ldc_(constantIndex); // Visit the constant to decide how it needs to be appended. programClass.constantPoolEntryAccept(constantIndex, this); break; default: // Find where the String stops and append it int nextArgIndex = nextArgIndex(concatenationRecipe, argIndex); estimatedStringLength += nextArgIndex - argIndex; appendChainComposer.ldc(concatenationRecipe.substring(argIndex, nextArgIndex)) .invokevirtual(ClassConstants.NAME_JAVA_LANG_STRING_BUILDER, ClassConstants.METHOD_NAME_APPEND, ClassConstants.METHOD_TYPE_STRING_STRING_BUILDER); // Jump forward to the end of the String argIndex = nextArgIndex - 1; break; } } // Create a StringBuilder with the estimated initial size mainReplacementComposer.new_( ClassConstants.NAME_JAVA_LANG_STRING_BUILDER) .dup() .pushInt( estimatedStringLength) .invokespecial(ClassConstants.NAME_JAVA_LANG_STRING_BUILDER, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INT_VOID); // Attach the 'append' instruction chain mainReplacementComposer.appendInstructions(appendChainComposer.instructions()); // Finish with StringBuilder.toString() mainReplacementComposer.invokevirtual(ClassConstants.NAME_JAVA_LANG_STRING_BUILDER, ClassConstants.METHOD_NAME_TOSTRING, ClassConstants.METHOD_TYPE_TOSTRING); // Commit the code changes codeAttributeEditor.replaceInstruction(offset, mainReplacementComposer.instructions()); // Optionally let this instruction be visited some more if (extraInstructionVisitor != null) { extraInstructionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); } } } } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { bootstrapMethodsAttribute.bootstrapMethodEntryAccept(clazz, referencedBootstrapMethodIndex, this); } // Implementations for BootstrapMethodInfoVisitor. @Override public void visitBootstrapMethodInfo(Clazz clazz, BootstrapMethodInfo bootstrapMethodInfo) { ProgramClass programClass = (ProgramClass) clazz; MethodHandleConstant bootstrapMethodHandle = (MethodHandleConstant) programClass.getConstant(bootstrapMethodInfo.u2methodHandleIndex); if (isStringConcatFactory(bootstrapMethodHandle.getClassName(clazz))) { concatenationRecipe = ((StringConstant) programClass.getConstant(bootstrapMethodInfo.u2methodArguments[0])).getString(programClass); concatenationConstants = bootstrapMethodInfo.u2methodArgumentCount > 1 ? Arrays.copyOfRange(bootstrapMethodInfo.u2methodArguments, 1, bootstrapMethodInfo.u2methodArgumentCount) : new int[0]; } } // Implementations for ConstantVisitor. @Override public void visitAnyConstant(Clazz clazz, Constant constant) { // append as Object by default. Override below if necessary. estimatedStringLength += 16; appendChainComposer.invokevirtual(ClassConstants.NAME_JAVA_LANG_STRING_BUILDER, ClassConstants.METHOD_NAME_APPEND, ClassConstants.METHOD_TYPE_OBJECT_STRING_BUILDER); } @Override public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { estimatedStringLength += stringConstant.getString(clazz).length(); appendChainComposer.invokevirtual(ClassConstants.NAME_JAVA_LANG_STRING_BUILDER, ClassConstants.METHOD_NAME_APPEND, ClassConstants.METHOD_TYPE_STRING_STRING_BUILDER); } // Small utility methods. private static boolean isStringConcatFactory(String className) { return ClassConstants.NAME_JAVA_LANG_INVOKE_STRING_CONCAT_FACTORY.equals(className); } private static boolean isMakeConcat(String methodName) { return ClassConstants.METHOD_NAME_MAKE_CONCAT.equals(methodName); } private static boolean isMakeConcatWithConstants(String methodName) { return ClassConstants.METHOD_NAME_MAKE_CONCAT_WITH_CONSTANTS.equals(methodName); } private static int typicalStringLengthFromType(String internalTypeName) { return internalTypeName.equals(String.valueOf(TypeConstants.BOOLEAN)) ? MAXIMUM_BOOLEAN_AS_STRING_LENGTH : internalTypeName.equals(String.valueOf(TypeConstants.CHAR)) ? MAXIMUM_CHAR_AS_STRING_LENGTH : internalTypeName.equals(String.valueOf(TypeConstants.BYTE)) ? MAXIMUM_BYTE_AS_STRING_LENGTH : internalTypeName.equals(String.valueOf(TypeConstants.SHORT)) ? MAXIMUM_SHORT_AS_STRING_LENGTH : internalTypeName.equals(String.valueOf(TypeConstants.INT)) ? MAXIMUM_INT_AS_STRING_LENGTH : internalTypeName.equals(String.valueOf(TypeConstants.LONG)) ? MAXIMUM_LONG_AS_STRING_LENGTH : internalTypeName.equals(String.valueOf(TypeConstants.FLOAT)) ? MAXIMUM_FLOAT_AS_STRING_LENGTH : internalTypeName.equals(String.valueOf(TypeConstants.DOUBLE)) ? MAXIMUM_DOUBLE_AS_STRING_LENGTH : DEFAULT_STRINGBUILDER_INIT_SIZE ; } private static String appendDescriptorFromInternalType(String internalTypeName) { return internalTypeName.equals(String.valueOf(TypeConstants.BOOLEAN)) ? ClassConstants.METHOD_TYPE_BOOLEAN_STRING_BUILDER : internalTypeName.equals(String.valueOf(TypeConstants.CHAR)) ? ClassConstants.METHOD_TYPE_CHAR_STRING_BUILDER : internalTypeName.equals(String.valueOf(TypeConstants.BYTE)) || internalTypeName.equals(String.valueOf(TypeConstants.SHORT)) || internalTypeName.equals(String.valueOf(TypeConstants.INT)) ? ClassConstants.METHOD_TYPE_INT_STRING_BUILDER : internalTypeName.equals(String.valueOf(TypeConstants.LONG)) ? ClassConstants.METHOD_TYPE_LONG_STRING_BUILDER : internalTypeName.equals(String.valueOf(TypeConstants.FLOAT)) ? ClassConstants.METHOD_TYPE_FLOAT_STRING_BUILDER : internalTypeName.equals(String.valueOf(TypeConstants.DOUBLE)) ? ClassConstants.METHOD_TYPE_DOUBLE_STRING_BUILDER : internalTypeName.equals(ClassConstants.NAME_JAVA_LANG_STRING) ? ClassConstants.METHOD_TYPE_STRING_STRING_BUILDER : ClassConstants.METHOD_TYPE_OBJECT_STRING_BUILDER; } private static int nextArgIndex(String recipe, int fromIndex) { for(int i = fromIndex; i < recipe.length(); i++) { char c = recipe.charAt(i); if (c == C_VARIABLE_ARGUMENT || c == C_CONSTANT_ARGUMENT) { return i; } } return recipe.length(); } } ================================================ FILE: base/src/main/java/proguard/classfile/ClassMemberPair.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.classfile; import proguard.classfile.visitor.MemberVisitor; import java.util.Objects; /** * Container class for a pair of class + member. * * @author James Hamilton */ public class ClassMemberPair { public final Clazz clazz; public final Member member; public ClassMemberPair(Clazz clazz, Member member) { this.clazz = clazz; this.member = member; } public void accept(MemberVisitor memberVisitor) { this.member.accept(this.clazz, memberVisitor); } public String getName() { return this.member.getName(this.clazz); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ClassMemberPair that = (ClassMemberPair)o; return Objects.equals(clazz, that.clazz) && Objects.equals(member, that.member); } @Override public int hashCode() { return Objects.hash(clazz, member); } @Override public String toString() { return clazz.getName() + "." + this.member.getName(this.clazz) + this.member.getDescriptor(this.clazz); } } ================================================ FILE: base/src/main/java/proguard/classfile/pass/PrimitiveArrayConstantIntroducer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.classfile.pass; import proguard.AppView; import proguard.classfile.util.ArrayInitializationReplacer; import proguard.pass.Pass; /** * This pass replaces primitive array initialization code by primitive array constants. */ public class PrimitiveArrayConstantIntroducer implements Pass { @Override public void execute(AppView appView) { appView.programClassPool.classesAccept(new ArrayInitializationReplacer()); } } ================================================ FILE: base/src/main/java/proguard/classfile/visitor/InjectedClassFilter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.classfile.visitor; import proguard.classfile.*; import proguard.util.ProcessingFlags; /** * This ClassVisitor delegates to one of two other visitors, depending on * whether the visited class was injected or not. * * @author Johan Leys */ public class InjectedClassFilter implements ClassVisitor { private final ClassVisitor injectedClassVisitor; private final ClassVisitor otherClassVisitor; public InjectedClassFilter(ClassVisitor injectedClassVisitor, ClassVisitor otherClassVisitor) { this.injectedClassVisitor = injectedClassVisitor; this.otherClassVisitor = otherClassVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { ClassVisitor delegate = delegateVisitor(programClass); if (delegate != null) { delegate.visitProgramClass(programClass); } } @Override public void visitLibraryClass(LibraryClass libraryClass) { if (otherClassVisitor != null) { otherClassVisitor.visitLibraryClass(libraryClass); } } // Small utility methods. private ClassVisitor delegateVisitor(ProgramClass programClass) { return (programClass.processingFlags & ProcessingFlags.INJECTED) != 0 ? injectedClassVisitor : otherClassVisitor; } } ================================================ FILE: base/src/main/java/proguard/configuration/ConfigurationLogger.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2023 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.configuration; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * This class can be injected in applications to log information about reflection * being used in the application code, and suggest appropriate ProGuard rules for * keeping the reflected classes, methods and/or fields. * * @author Johan Leys */ public class ConfigurationLogger implements Runnable { // Logging constants. private static final boolean LOG_ONCE = false; private static final String ANDROID_UTIL_LOG = "android.util.Log"; private static final String LOG_TAG = "ProGuard"; // Java API constants. private static final String CLASS_CLASS = "Class"; private static final String CLASS_CLASS_LOADER = "ClassLoader"; private static final String METHOD_FOR_NAME = "forName"; private static final String METHOD_LOAD_CLASS = "loadClass"; private static final String METHOD_GET_DECLARED_FIELD = "getDeclaredField"; private static final String METHOD_GET_FIELD = "getField"; private static final String METHOD_GET_DECLARED_CONSTRUCTOR = "getDeclaredConstructor"; private static final String METHOD_GET_CONSTRUCTOR = "getConstructor"; private static final String METHOD_GET_DECLARED_METHOD = "getDeclaredMethod"; private static final String METHOD_GET_METHOD = "getMethod"; // Configuration constants. private static final String KEEP = "-keep"; private static final String KEEP_CLASS_MEMBERS = "-keepclassmembers"; // Configuration files. public static final String CLASS_MAP_FILENAME = "classmap.txt"; // Class processing flags. public static final int CLASS_KEPT = 1 << 0; public static final int ALL_DECLARED_CONSTRUCTORS_KEPT = 1 << 1; public static final int ALL_PUBLIC_CONSTRUCTORS_KEPT = 1 << 2; public static final int ALL_DECLARED_FIELDS_KEPT = 1 << 3; public static final int ALL_PUBLIC_FIELDS_KEPT = 1 << 4; public static final int ALL_DECLARED_METHODS_KEPT = 1 << 5; public static final int ALL_PUBLIC_METHODS_KEPT = 1 << 6; public static final int CLASS_SHRUNK = 1 << 7; // Member processing flags. public static final int MEMBER_KEPT = 1 << 0; public static final int MEMBER_SHRUNK = 1 << 1; private static final String EMPTY_LINE = "\u00a0\n"; private static final String INIT = ""; // Constants for the FNV1-a hashCode algorithm. private static final int FNV_HASH_INIT = 0x811c9dc5; private static final int FNV_HASH_PRIME = 0x01000193; // Android logging method. private static final Method logMethod = getLogMethod(); // Configuration and processing information about classes, class members, resources, ... // Map from obfuscated class names to class infos. private static final Map sObfuscatedClassNameInfoMap = new HashMap<>(); // Data structures to keep track of which suggestions have already been logged. // Set with missing class names. private static final Set sMissingClasses = new HashSet<>(); // Map from class name to missing field names. private static final Map> sMissingFields = new HashMap<>(); // Map from class name to missing method signatures. private static final Map> sMissingMethods = new HashMap<>(); // Set of classes on which getFields or getDeclaredFields is invoked. private static final Set sFieldListingCLasses = new HashSet<>(); // Set of classes on which getConstructors or getDeclaredConstructors is invoked. private static final Set sConstructorListingClasses = new HashSet<>(); // Set of classes on which getMethods or getDeclaredMethods is invoked. private static final Set sMethodListingClasses = new HashSet<>(); static { // Initialize all configuration and processing information data structures. try { initializeClassMap(); } catch (Exception e) { throw new RuntimeException(e); } } // Classes. /** * Check if a class that is loaded via Class.forName() is kept and if not, * log a keep rule suggestion for it. * * @param reflectedClassName the name of the class that is introspected. * @param callingClassName the class from which the reflection API is called. */ public static void checkForName(String reflectedClassName, String callingClassName) { checkClass(CLASS_CLASS, METHOD_FOR_NAME, reflectedClassName, callingClassName); } /** * Check if a class that is loaded via ClassLoader.loadClass() is kept and if not, * log a keep rule suggestion for it. * * @param reflectedClassName the name of the class that is introspected. * @param callingClassName the class from which the reflection API is called. */ public static void checkLoadClass(String reflectedClassName, String callingClassName) { checkClass(CLASS_CLASS_LOADER, METHOD_LOAD_CLASS, reflectedClassName, callingClassName); } /** * Check if a class that is loaded via reflection is kept and if not, * log a keep rule suggestion for it. * * @param reflectedClassName the name of the class that is introspected. * @param callingClassName the class from which the reflection API is called. */ public static void checkClass(String reflectionClassName, String reflectionMethodName, String reflectedClassName, String callingClassName) { ClassInfo classInfo = sObfuscatedClassNameInfoMap.get(reflectedClassName); //classInfo will be null if the class was unavailable or is a library class, //in this case a keep rule doesn't make sense anyway if (classInfo == null) { return; } //at this point the class was in the original program class pool //do not log already kept classes (where the user already put a -keep rule in the config) if (isKept(classInfo)) { return; } if (shouldLog(sMissingClasses, reflectedClassName)) { log("The class '" + originalClassName(callingClassName) + "' " + "is calling " + reflectionClassName + "." + reflectionMethodName + " to retrieve\n" + "the class '" + reflectedClassName + "'" + (originalClassName(reflectedClassName).equals(reflectedClassName) ? "" : "(originally '" + originalClassName(reflectedClassName) + "')") + ", but there is no rule to keep the class.\n" + "You should consider preserving the class,\n" + "with a setting like:\n" + EMPTY_LINE + keepClassRule(originalClassName(reflectedClassName)) ); } } // Fields. /** * Check if a field that is retrieved via Class.getField() is kept and if not, * log a keep rule suggestion for it. * * @param reflectedClass the class that is introspected. * @param reflectedFieldName the field that is retrieved via reflection. * @param callingClassName the class from which the reflection API is called. */ public static void checkGetField(Class reflectedClass, String reflectedFieldName, String callingClassName) { checkGetField(METHOD_GET_FIELD, reflectedClass, reflectedFieldName, callingClassName); } /** * Check if a field that is retrieved via Class.getDeclaredField() is kept and if not, * log a keep rule suggestion for it. * * @param reflectedClass the class that is introspected. * @param reflectedFieldName the field that is retrieved via reflection. * @param callingClassName the class from which the reflection API is called. */ public static void checkGetDeclaredField(Class reflectedClass, String reflectedFieldName, String callingClassName) { checkGetField(METHOD_GET_DECLARED_FIELD, reflectedClass, reflectedFieldName, callingClassName); } /** * Check if a field that is retrieved via reflection is kept and if not, * log a keep rule suggestion for it. * * @param reflectionMethodName the method of the Java reflection API that is invoked. * @param reflectedClass the class that is introspected. * @param reflectedFieldName the field that is retrieved via reflection. * @param callingClassName the class from which the reflection API is called. */ private static void checkGetField(String reflectionMethodName, Class reflectedClass, String reflectedFieldName, String callingClassName) { MemberInfo fieldInfo = getDeclaringClass(reflectedClass, reflectedFieldName); if (fieldInfo != null && !isKept(fieldInfo) && shouldLog(reflectedClass, sMissingFields, reflectedFieldName)) { String keepClassName = fieldInfo.declaringClassName; String reflectedClassName = originalClassName(reflectedClass.getName()); log("The class '" + originalClassName(callingClassName) + "' is calling Class." + reflectionMethodName + "\n" + "on class '" + originalClassName(reflectedClass) + "' to retrieve the field '" + reflectedFieldName + "'" + (!reflectedClassName.equals(keepClassName) ? " (declared in class '" + keepClassName + "')": "") + ",\n but there is no rule to keep the field." + "\n" + "You should consider preserving it, with a rule like:" + "\n" + EMPTY_LINE + keepFieldRule(keepClassName, reflectedFieldName) + "\n" + EMPTY_LINE); } } /** * Check if the fields of a class whose fields are retrieved via Class.getDeclaredFields() are all kept and if not, * log a keep rule suggestion for it. * * @param callingClassName the class from which the reflection API is called. * @param reflectedClass the class that is introspected. */ public static void checkGetDeclaredFields(Class reflectedClass, String callingClassName) { if (!allDeclaredFieldsKept(reflectedClass) && shouldLog(sFieldListingCLasses, reflectedClass)) { log("The class '" + originalClassName(callingClassName) + "' is calling Class." + "getDeclaredFields" + "\n" + "on class '" + originalClassName(reflectedClass) + "' to retrieve its fields.\n" + "You might consider preserving all fields with their original names,\n" + "with a setting like:\n" + EMPTY_LINE + keepAllFieldsRule(reflectedClass) + "\n" + EMPTY_LINE); } } /** * Check if the fields of a class whose fields are retrieved via Class.getFields() are all kept and if not, * log a keep rule suggestion for it. * * @param reflectedClass the class that is introspected. * @param callingClassName the class from which the reflection API is called. */ public static void checkGetFields(Class reflectedClass, String callingClassName) { if (!allPublicFieldsKept(reflectedClass) && shouldLog(sFieldListingCLasses, reflectedClass)) { log("The class '" + originalClassName(callingClassName) + "' is calling Class." + "getFields" + "\n" + "on class '" + originalClassName(reflectedClass) + "' to retrieve its fields.\n" + "You might consider preserving all public fields with their original names,\n" + "with a setting like:\n" + EMPTY_LINE + keepAllPublicFieldsRule(reflectedClass) + "\n" + EMPTY_LINE); } } // Constructors. /** * Check if a constructor that is retrieved via Class.getDeclaredConstructor() is kept and if not, * log a keep rule suggestion for it. */ public static void checkGetDeclaredConstructor(Class reflectedClass, Class[] constructorParameters, String callingClassName) { checkGetConstructor(METHOD_GET_DECLARED_CONSTRUCTOR, reflectedClass, constructorParameters, callingClassName); } /** * Check if a constructor that is retrieved via Class.getConstructor() is kept and if not, * log a keep rule suggestion for it. */ public static void checkGetConstructor(Class reflectedClass, Class[] constructorParameters, String callingClassName) { checkGetConstructor(METHOD_GET_CONSTRUCTOR, reflectedClass, constructorParameters, callingClassName); } /** * Check if a constructor that is retrieved via reflection is kept and if not, * log a keep rule suggestion for it. */ public static void checkGetConstructor(String reflectionMethodName, Class reflectedClass, Class[] constructorParameters, String callingClassName) { MemberInfo constructorInfo = getDeclaringClass(reflectedClass, INIT, constructorParameters, false); if (constructorInfo != null && !isKept(constructorInfo) && (constructorParameters == null || constructorParameters.length > 0)) { String signature = signatureString(INIT, constructorParameters, true); if (shouldLog(reflectedClass, sMissingMethods, signature)) { String keepClassName = reflectedClass.getName(); log("The class '" + originalClassName(callingClassName) + "' is calling Class." + reflectionMethodName + "\n" + "on class '" + originalClassName(reflectedClass) + "' to retrieve\n" + "the constructor with signature " + signature + ", " + "but there is no rule to keep the constructor." + "\n" + "You should consider preserving it, with a rule like:" + "\n" + EMPTY_LINE + keepConstructorRule(keepClassName, signature) + "\n" + EMPTY_LINE); } } } /** * Check if the constructors of a class on which getDeclaredConstructors() is called are all kept and if not, * log a keep rule suggestion for it. * * @param reflectedClass the class that is introspected. * @param callingClassName the class from which the reflection API is called. */ public static void checkGetDeclaredConstructors(Class reflectedClass, String callingClassName) { if (!allDeclaredConstructorsKept(reflectedClass) && shouldLog(sConstructorListingClasses, reflectedClass)) { log("The class '" + originalClassName(callingClassName) + "' is calling Class.getDeclaredConstructors" + "\n" + "on class '" + originalClassName(reflectedClass) + "' to retrieve its constructors.\n" + "You might consider preserving all constructors with their original names,\n" + "with a setting like:\n" + EMPTY_LINE + keepAllConstructorsRule(reflectedClass) + "\n" + EMPTY_LINE); } } /** * Check if the constructors of a class on which getConstructors() is called are all kept and if not, * log a keep rule suggestion for it. * * @param reflectedClass the class that is introspected. * @param callingClassName the class from which the reflection API is called. */ public static void checkGetConstructors(Class reflectedClass, String callingClassName) { if (!allPublicConstructorsKept(reflectedClass) && shouldLog(sConstructorListingClasses, reflectedClass)) { log("The class '" + originalClassName(callingClassName) + "' is calling Class.getConstructors" + "\n" + "on class '" + originalClassName(reflectedClass) + "' to retrieve its constructors.\n" + "You might consider preserving all constructors with their original names,\n" + "with a setting like:\n" + EMPTY_LINE + keepAllConstructorsRule(reflectedClass) + "\n" + EMPTY_LINE); } } // Methods. /** * Check if a method that is retrieved via Class.getDeclaredMethod() is kept and if not, * log a keep rule suggestion for it. * * @param reflectedClass the class that is introspected. * @param reflectedMethodName the method that is retrieved via reflection. * @param reflectedMethodParameters the parameters of the method that is retrieved via reflection. * @param callingClassName the class from which the reflection API is called. */ public static void checkGetDeclaredMethod(Class reflectedClass, String reflectedMethodName, Class[] reflectedMethodParameters, String callingClassName) { checkGetMethod(METHOD_GET_DECLARED_METHOD, reflectedClass, reflectedMethodName, reflectedMethodParameters, callingClassName); } /** * Check if a method that is retrieved via Class.getMethod() is kept and if not, * log a keep rule suggestion for it. * * @param reflectedClass the class that is introspected. * @param reflectedMethodName the method that is retrieved via reflection. * @param reflectedMethodParameters the parameters of the method that is retrieved via reflection. * @param callingClassName the class from which the reflection API is called. */ public static void checkGetMethod(Class reflectedClass, String reflectedMethodName, Class[] reflectedMethodParameters, String callingClassName) { checkGetMethod(METHOD_GET_METHOD, reflectedClass, reflectedMethodName, reflectedMethodParameters, callingClassName); } /** * Check if a method that is retrieved via reflection is kept and if not, * log a keep rule suggestion for it. * * @param reflectionMethodName the method of the Java reflection API that is invoked. * @param reflectedClass the class that is introspected. * @param reflectedMethodName the method that is retrieved via reflection. * @param reflectedMethodParameters the parameters of the method that is retrieved via reflection. * @param callingClassName the class from which the reflection API is called. */ private static void checkGetMethod(String reflectionMethodName, Class reflectedClass, String reflectedMethodName, Class[] reflectedMethodParameters, String callingClassName ) { MemberInfo methodInfo = getDeclaringClass(reflectedClass, reflectedMethodName, reflectedMethodParameters, true); if (methodInfo != null && !isKept(methodInfo)) { String signature = signatureString(reflectedMethodName, reflectedMethodParameters, true); if (shouldLog(reflectedClass, sMissingMethods, signature)) { String keepClassName = methodInfo.declaringClassName; String reflectedClassName = originalClassName(reflectedClass); log("The class '" + originalClassName(callingClassName) + "' is calling Class." + reflectionMethodName + "\n" + "on class '" + reflectedClassName + "' to retrieve the method\n" + signature + (!reflectedClassName.equals(keepClassName) ? " (declared in class '" + keepClassName + "')": "") + ", but there is no rule to keep the method." + "\n" + "You should consider preserving it, with a rule like:" + "\n" + EMPTY_LINE + keepMethodRule(keepClassName, signature) + "\n" + EMPTY_LINE); } } } /** * Check if the methods of a class on which getDeclaredMethods() is called are all kept and if not, * log a keep rule suggestion for it. * * @param reflectedClass the class that is introspected. * @param callingClassName the class from which the reflection API is called. */ public static void checkGetDeclaredMethods(Class reflectedClass, String callingClassName) { if (!allDeclaredMethodsKept(reflectedClass) && shouldLog(sMethodListingClasses, reflectedClass)) { log("The class '" + originalClassName(callingClassName) + "' is calling Class." + "getDeclaredMethods" + "\n" + "on class '" + originalClassName(reflectedClass) + "' to retrieve its methods.\n" + "You might consider preserving all methods with their original names,\n" + "with a setting like:\n" + EMPTY_LINE + keepAllMethodsRule(reflectedClass) + "\n" + EMPTY_LINE); } } /** * Check if the methods of a class on which getMethods() is called are all kept and if not, * log a keep rule suggestion for it. * * @param reflectedClass the class that is introspected. * @param callingClassName the class from which the reflection API is called. */ public static void checkGetMethods(Class reflectedClass, String callingClassName) { if (!allPublicMethodsKept(reflectedClass) && shouldLog(sMethodListingClasses, reflectedClass)) { log("The class '" + originalClassName(callingClassName) + "' is calling Class." + "getMethods" + "\n" + "on class '" + originalClassName(reflectedClass) + "' to retrieve its methods.\n" + "You might consider preserving all public methods with their original names,\n" + "with a setting like:\n" + EMPTY_LINE + keepAllPublicMethodsRule(reflectedClass) + "\n" + EMPTY_LINE); } } /** * Returns whether the given member is kept on the specified class. * * @param memberInfo Member info * @return true if the member is kept */ private static boolean isKept(MemberInfo memberInfo) { return memberInfo != null && (memberInfo.flags & MEMBER_KEPT) != 0; } /** * Is the member shrunk away? * * @param memberInfo Member information. * @return true if it's not in the classpool anymore. */ private static boolean isShrunk(MemberInfo memberInfo) { return memberInfo != null && (memberInfo.flags & MEMBER_SHRUNK) != 0; } /** * Is the class kept? * * @param classInfo Class information. * @return true if it's kept. */ private static boolean isKept(ClassInfo classInfo) { return classInfo != null && (classInfo.flags & CLASS_KEPT) != 0; } /** * Is the class shrunk away? * * @param classInfo Class information. * @return true if it's not in the classpool anymore. */ private static boolean isShrunk(ClassInfo classInfo) { return classInfo != null && (classInfo.flags & CLASS_SHRUNK) != 0; } /** * Returns the declaring class of a given field by looking at the hierarchy * of the given class. */ private static MemberInfo getDeclaringClass(Class reflectedClass, String fieldName) { String className = reflectedClass.getName(); while (className != null) { ClassInfo classInfo = sObfuscatedClassNameInfoMap.get(className); if (classInfo != null) { int signatureHash = hashFnv1a32_UTF8(fieldName); for (int i = 0; i < classInfo.fieldHashes.length; i++) { if (classInfo.fieldHashes[i] == signatureHash) { return new MemberInfo(classInfo.originalClassName, classInfo.fieldFlags[i]); } } className = classInfo.superClassName; } else { className = null; } } return null; } /** * Returns the declaring class of a given method by looking at the hierarchy * of the given class. */ private static MemberInfo getDeclaringClass(Class reflectedClass, String methodName, Class[] parameters, boolean checkHierarchy) { String className = reflectedClass.getName(); while (className != null) { ClassInfo classInfo = sObfuscatedClassNameInfoMap.get(className); if (classInfo != null) { int signatureHash = hashFnv1a32_UTF8(signatureString(methodName, parameters, false)); for (int i = 0; i < classInfo.methodHashes.length; i++) { if (classInfo.methodHashes[i] == signatureHash) { return new MemberInfo(classInfo.originalClassName, classInfo.methodFlags[i]); } } className = checkHierarchy ? classInfo.superClassName : null; } else { className = null; } } return null; } /** * Returns whether all declared fields of the given class are kept * (not including fields in super classes). */ private static boolean allDeclaredFieldsKept(Class clazz) { ClassInfo classInfo = sObfuscatedClassNameInfoMap.get(clazz.getName()); return classInfo != null && (classInfo.flags & ALL_DECLARED_FIELDS_KEPT) != 0; } /** * Returns whether all public fields of the given class are kept * (including fields in super classes). */ private static boolean allPublicFieldsKept(Class clazz) { ClassInfo classInfo = sObfuscatedClassNameInfoMap.get(clazz.getName()); return classInfo != null && (classInfo.flags & ALL_PUBLIC_FIELDS_KEPT) != 0; } /** * Returns whether all declared constructors of the given class are kept. */ private static boolean allDeclaredConstructorsKept(Class clazz) { ClassInfo classInfo = sObfuscatedClassNameInfoMap.get(clazz.getName()); return classInfo != null && (classInfo.flags & ALL_DECLARED_CONSTRUCTORS_KEPT) != 0; } /** * Returns whether all public constructors of the given class are kept. */ private static boolean allPublicConstructorsKept(Class clazz) { ClassInfo classInfo = sObfuscatedClassNameInfoMap.get(clazz.getName()); return classInfo != null && (classInfo.flags & ALL_PUBLIC_CONSTRUCTORS_KEPT) != 0; } /** * Returns whether all declared methods of the given class are kept * (not including methods in super classes). */ private static boolean allDeclaredMethodsKept(Class clazz) { ClassInfo classInfo = sObfuscatedClassNameInfoMap.get(clazz.getName()); return classInfo != null && (classInfo.flags & ALL_DECLARED_METHODS_KEPT) != 0; } /** * Returns whether all public methods of the given class are kept * (including methods in super classes). */ private static boolean allPublicMethodsKept(Class clazz) { ClassInfo classInfo = sObfuscatedClassNameInfoMap.get(clazz.getName()); return classInfo != null && (classInfo.flags & ALL_PUBLIC_METHODS_KEPT) != 0; } /** * Returns a String of concatenated class names for the given classes, separated by a comma. */ private static String signatureString(String methodName, Class[] parameters, boolean deobfuscate) { StringBuilder builder = new StringBuilder(); builder.append(methodName); builder.append("("); if (parameters != null) { for (int i = 0; i < parameters.length; i++) { if (i != 0) { builder.append(","); } builder.append(deobfuscate ? originalClassName(parameters[i]) : parameters[i].getName()); } } builder.append(")"); return builder.toString(); } // Implementations for Runnable. public void run() { printConfiguration(); } private static void printConfiguration() { log("The following settings may help solving issues related to\n" + "missing classes, methods and/or fields:\n"); for (String clazz : sMissingClasses) { log(keepClassRule(clazz) + "\n"); } for (String clazz : sMissingMethods.keySet()) { for (String method : sMissingMethods.get(clazz)) { log(keepMethodRule(clazz, method) + "\n"); } } for (String clazz : sMissingFields.keySet()) { for (String field : sMissingFields.get(clazz)) { log(keepFieldRule(clazz, field) + "\n"); } } } // Helper methods to print rules. private static String keepClassRule(String className) { return KEEP + " class " + className; } private static String keepConstructorRule(String className, String originalMethodSignature) { return KEEP_CLASS_MEMBERS + " class " + originalClassName(className) + " {\n" + " " + originalMethodSignature + ";\n" + "}"; } private static String keepMethodRule(String className, String originalMethodSignature) { return KEEP_CLASS_MEMBERS + " class " + originalClassName(className) + " {\n" + " *** " + originalMethodSignature + ";\n" + "}"; } private static String keepFieldRule(String className, String fieldName) { return KEEP_CLASS_MEMBERS + " class " + originalClassName(className) + " {\n" + " *** " + fieldName + ";\n" + "}"; } private static String keepAllConstructorsRule(Class className) { return KEEP_CLASS_MEMBERS + " class " + originalClassName(className) + " {\n" + " (...);\n" + "}"; } private static String keepAllMethodsRule(Class className) { return KEEP_CLASS_MEMBERS + " class " + originalClassName(className) + " {\n" + " ;\n" + "}"; } private static String keepAllPublicMethodsRule(Class className) { return KEEP_CLASS_MEMBERS + " class " + originalClassName(className) + " {\n" + " public ;\n" + "}"; } private static String keepAllFieldsRule(Class className) { return KEEP_CLASS_MEMBERS + " class " + originalClassName(className) + " {\n" + " ;\n" + "}"; } private static String keepAllPublicFieldsRule(Class className) { return KEEP_CLASS_MEMBERS + " class " + originalClassName(className) + " {\n" + " public ;\n" + "}"; } private static String originalClassName(Class className) { return originalClassName(className.getName()); } private static String originalClassName(String className) { // The dimension of the array, if className is an array type. // 0 otherwise. int arrayDimension = className.lastIndexOf("[") + 1; // Normalize array-type class names. if (arrayDimension != 0) { // Remove brackets to look up the external class name. className = getExternalClassNameFromComponentType(className.substring(arrayDimension)); } // Look up original class in mappings. ClassInfo classInfo = sObfuscatedClassNameInfoMap.get(className); String originalClassName = classInfo != null ? classInfo.originalClassName : className; return addArrayBrackets(originalClassName, arrayDimension); } /** * Appends the necessary array brackets to the end of the given classname. * eg: className : "java.lang.String" * arrayDimension : 2 * return : "java.lang.String[][]" */ private static String addArrayBrackets(String className, int arrayDimension) { StringBuilder sb = new StringBuilder(className); for (int index = 0; index < arrayDimension; index++) { sb.append("[]"); } return sb.toString(); } /** * Returns the external class name for a given component type * of an array class type. *

* The following are examples of what Class.getName() returns: * byte.class.getName() : "byte" * String.class.getName() : "java.lang.String" * byte[].class.getName() : "[B" * String[].class.getName(): "[Ljava.lang.String;" * * @link https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3 */ private static String getExternalClassNameFromComponentType(String componentType) { switch (componentType.charAt(0)) { case 'V': return "void"; case 'Z': return "boolean"; case 'B': return "byte"; case 'C': return "char"; case 'S': return "short"; case 'I': return "int"; case 'J': return "long"; case 'F': return "float"; case 'D': return "double"; case 'L': return componentType.substring(1, componentType.length() - 1); } throw new IllegalArgumentException("Unknown component type ["+componentType+"]"); } /** * Returns whether the given class is a library class or not. * * It's a library class if it's not in the classmap.txt file. */ private static boolean isLibraryClass(Class clazz) { return !sObfuscatedClassNameInfoMap.containsKey(clazz.getName()); } // Initialization. /** * Returns an Android logging method, or null if it can't be found. */ private static Method getLogMethod() { try { Class logClass = Class.forName(ANDROID_UTIL_LOG); return logClass.getMethod("w", String.class, String. class); } catch (Exception e) { return null; } } /** * Initializes all class-specific processing information. */ private static void initializeClassMap() throws IOException { loadClassMap(ConfigurationLogger.class.getClassLoader().getResourceAsStream(CLASS_MAP_FILENAME), sObfuscatedClassNameInfoMap); } /** * Load a classmap.txt file into a map. * * @param inputStream Input stream from which to read the info. * @param map The map to load the info into. * @throws IOException If the input stream could not be read. */ public static void loadClassMap(InputStream inputStream, Map map) throws IOException { if (inputStream == null) { return; } DataInputStream dataInputStream = new DataInputStream( new BufferedInputStream(inputStream)); int classCount = dataInputStream.readInt(); for (int i = 0; i < classCount; i++) { String originalClassName = dataInputStream.readUTF (); String obfuscatedClassName = dataInputStream.readUTF (); String superClassName = dataInputStream.readUTF (); short flags = dataInputStream.readShort(); int fieldCount = dataInputStream.readShort(); int[] fieldHashes = new int[fieldCount]; byte[] fieldFlags = new byte[fieldCount]; for (int j = 0; j < fieldCount; j++) { // Name hash. fieldHashes[j] = dataInputStream.readInt(); fieldFlags[j] = dataInputStream.readByte(); } int methodCount = dataInputStream.readShort(); int[] methodHashes = new int[methodCount]; byte[] methodFlags = new byte[methodCount]; for (int j = 0; j < methodCount; j++) { // Signature hash. methodHashes[j] = dataInputStream.readInt(); methodFlags[j] = dataInputStream.readByte(); } ClassInfo classInfo = new ClassInfo(originalClassName, superClassName, flags, fieldHashes, fieldFlags, methodHashes, methodFlags); map.put(obfuscatedClassName, classInfo); } } // Logging utility methods. private static boolean shouldLog(Class reflectedClass, Map> classValuesMap, T value) { if (isLibraryClass(reflectedClass)) { return false; } Set values = computeIfAbsent(classValuesMap, reflectedClass.getName()); return shouldLog(values, value); } private static boolean shouldLog(Set classes, Class reflectedClass) { return !isLibraryClass(reflectedClass) && shouldLog(classes, reflectedClass.getName()); } private static boolean shouldLog(Set values, T value) { return !LOG_ONCE || values.add(value); } private static Set computeIfAbsent(Map> map, String key ) { Set set = map.get(key); if (set == null) { set = new HashSet(); map.put(key, set); } return set; } /** * Log a message, either on the Android Logcat, if available, or on the * Standard error output stream otherwise. * * @param message the message to be logged. */ private static void log(String message) { if (logMethod != null) { try { logMethod.invoke(null, LOG_TAG, message); } catch (Exception e) { System.err.println(message); } } else { System.err.println(message); } } // Hashing utility methods. /** * Convenience method for computing the FNV-1a hash on the UTF-8 encoded byte representation of the given String. */ private static int hashFnv1a32_UTF8(String string) { return hash(string, FNV_HASH_INIT); } /** * Convenience method for computing the FNV-1a hash on the UTF-8 encoded byte representation of the given String, * using the given initial hash value. */ private static int hash(String string, int init) { return hash(string.getBytes(StandardCharsets.UTF_8), init); } /** * Computes a hash of the given bytes, using the FNV-1a hashing function, but with a custom inital hash value. */ private static int hash(byte[] data, int init) { int hash = init; for (byte b : data) { hash ^= b; hash *= FNV_HASH_PRIME; } return hash; } /** * Container of processing information of a class. */ public static class ClassInfo { final String originalClassName; final String superClassName; final short flags; public final int[] fieldHashes; final byte[] fieldFlags; public final int[] methodHashes; final byte[] methodFlags; ClassInfo(String originalClassName, String superClassName, short flags, int[] fieldHashes, byte[] fieldFlags, int[] methodHashes, byte[] methodFlags) { this.originalClassName = originalClassName; this.superClassName = superClassName; this.flags = flags; this.fieldHashes = fieldHashes; this.fieldFlags = fieldFlags; this.methodHashes = methodHashes; this.methodFlags = methodFlags; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.originalClassName); if (!superClassName.equals("")) sb.append(" extends ").append(superClassName); sb.append(" ("); if ((this.flags & CLASS_KEPT) != 0) sb.append("kept"); else sb.append("not kept"); if ((this.flags & CLASS_SHRUNK) != 0) sb.append(", shrunk"); else sb.append(", not shrunk"); sb.append(")"); sb.append(" "); sb.append(fieldHashes.length).append(" fields, ").append(methodHashes.length).append(" methods"); return sb.toString(); } } public static class MemberInfo { final String declaringClassName; final byte flags; MemberInfo(String declaringClassName, byte flags) { this.declaringClassName = declaringClassName; this.flags = flags; } public String toString() { return this.declaringClassName + " (" + ((this.flags & MEMBER_KEPT) != 0 ? "kept" : "not kept") + ", " + ((this.flags & MEMBER_SHRUNK) != 0 ? "shrunk" : "not shrunk") + ")"; } } } ================================================ FILE: base/src/main/java/proguard/configuration/ConfigurationLoggingAdder.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.configuration; import proguard.AppView; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.editor.PeepholeEditor; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.BranchTargetFinder; import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassSubHierarchyInitializer; import proguard.classfile.visitor.AllMethodVisitor; import proguard.classfile.visitor.ClassPoolFiller; import proguard.classfile.visitor.ClassProcessingFlagFilter; import proguard.classfile.visitor.MultiClassVisitor; import proguard.io.ClassPathDataEntry; import proguard.io.ClassReader; import proguard.io.ExtraDataEntryNameMap; import proguard.pass.Pass; import proguard.util.ProcessingFlagSetter; import proguard.util.ProcessingFlags; import java.io.IOException; import static proguard.configuration.ConfigurationLoggingInstructionSequenceConstants.*; /** * This pass can add configuration debug logging code to all code that * relies on reflection. The added code prints suggestions on which keep * rules to add to ensure the reflection code will continue working after * obfuscation and shrinking. * * @author Johan Leys */ public class ConfigurationLoggingAdder implements Pass { /** * Instruments the given program class pool. */ @Override public void execute(AppView appView) throws IOException { // Load the logging utility classes in the program class pool. // TODO: The initialization could be incomplete if the loaded classes depend on one another. ClassReader classReader = new ClassReader(false, false, false, false, null, new MultiClassVisitor( new ClassPoolFiller(appView.programClassPool), new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool), new ClassSubHierarchyInitializer(), new ProcessingFlagSetter(ProcessingFlags.INJECTED ))); classReader.read(new ClassPathDataEntry(ConfigurationLogger.ClassInfo.class)); classReader.read(new ClassPathDataEntry(ConfigurationLogger.MemberInfo.class)); classReader.read(new ClassPathDataEntry(ConfigurationLogger.class)); // Initialize the ConfigurationLogger class with the actual packageName. initializeConfigurationLogger(appView.programClassPool); // Set up the instruction sequences and their replacements. ConfigurationLoggingInstructionSequenceConstants constants = new ConfigurationLoggingInstructionSequenceConstants(appView.programClassPool, appView.libraryClassPool); BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); // Replace the instruction sequences in all classes. // Do not add configuration debugging to any ProGuard runtime classes, // to avoid false positives. appView.programClassPool.classesAccept( new ClassProcessingFlagFilter(0, ProcessingFlags.INJECTED, new AllMethodVisitor( new AllAttributeVisitor( new PeepholeEditor(branchTargetFinder, codeAttributeEditor, new ConfigurationLoggingInstructionSequencesReplacer(constants.CONSTANTS, constants.RESOURCE, branchTargetFinder, codeAttributeEditor, new ExtraClassAdder(appView.extraDataEntryNameMap))))))); } /** * Initialized the ConfigurationLogger class by injecting the actual packageName. */ private void initializeConfigurationLogger(ClassPool programClassPool) { ProgramClass configurationLoggerClass = (ProgramClass) programClassPool.getClass(LOGGER_CLASS_NAME); if (configurationLoggerClass == null) { throw new RuntimeException("ConfigurationLogger class could not be found in the program classpool."); } } private static class ExtraClassAdder implements InstructionVisitor { private final ExtraDataEntryNameMap extraDataEntryNameMap; ExtraClassAdder(ExtraDataEntryNameMap extraDataEntryNameMap) { this.extraDataEntryNameMap = extraDataEntryNameMap; } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // Add a dependency from the modified class on the logging class. extraDataEntryNameMap.addExtraClassToClass(clazz, ConfigurationLogger.class); extraDataEntryNameMap.addExtraClassToClass(clazz, ConfigurationLogger.ClassInfo.class); extraDataEntryNameMap.addExtraClassToClass(clazz, ConfigurationLogger.MemberInfo.class); } } } ================================================ FILE: base/src/main/java/proguard/configuration/ConfigurationLoggingInstructionSequenceConstants.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.configuration; import proguard.classfile.ClassPool; import proguard.classfile.constant.Constant; import proguard.classfile.editor.InstructionSequenceBuilder; import proguard.classfile.instruction.Instruction; import proguard.classfile.util.ClassUtil; import proguard.classfile.util.InstructionSequenceMatcher; /** * This class contains a set of instruction sequences for accessing class * information via reflection, and replacement instructions that add logging * information on the reflection that is used. * * @author Johan Leys */ public class ConfigurationLoggingInstructionSequenceConstants { static final String LOGGER_CLASS_NAME = ClassUtil.internalClassName(ConfigurationLogger.class.getName()); // Matched constants. public static final int CLASS_NAME = 0x30000000; public static final int LOCAL_VARIABLE_INDEX_1 = 0x30000001; public static final int LOCAL_VARIABLE_INDEX_2 = 0x30000002; public static final int LOCAL_VARIABLE_INDEX_3 = 0x30000003; public static final int CONSTANT_INDEX = InstructionSequenceMatcher.X; public final Instruction[][][] RESOURCE; public final Constant[] CONSTANTS; /** * Creates a new instance of ResourceIdInstructionSequenceConstants, * with constants that reference classes from the given class pools. */ public ConfigurationLoggingInstructionSequenceConstants(ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); RESOURCE = new Instruction[][][] { // Classes. { // Automatically detected and kept - don't check anything. ____.ldc_(CONSTANT_INDEX) .invokestatic("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;").__(), ____.ldc_(CONSTANT_INDEX) .invokestatic("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;").__() }, { ____.invokestatic("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;").__(), ____.dup() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkForName", "(Ljava/lang/String;Ljava/lang/String;)V") .invokestatic("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;").__() }, { ____.invokestatic("java/lang/Class", "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;").__(), ____.dup_x2() .pop() .dup_x2() .pop() .dup_x2() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkForName", "(Ljava/lang/String;Ljava/lang/String;)V") .invokestatic("java/lang/Class", "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;").__() }, { ____.invokevirtual("java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;").__(), ____.dup() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkLoadClass", "(Ljava/lang/String;Ljava/lang/String;)V") .invokevirtual("java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;").__(), }, // Constructors. { ____.invokevirtual("java/lang/Class", "getDeclaredConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;").__(), ____.dup2() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetDeclaredConstructor", "(Ljava/lang/Class;[Ljava/lang/Class;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getDeclaredConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;").__() }, { ____.invokevirtual("java/lang/Class", "getConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;").__(), ____.dup2() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetConstructor", "(Ljava/lang/Class;[Ljava/lang/Class;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;").__() }, { ____.invokevirtual("java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;").__(), ____.dup() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetDeclaredConstructors", "(Ljava/lang/Class;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;").__() }, { ____.invokevirtual("java/lang/Class", "getConstructors", "()[Ljava/lang/reflect/Constructor;").__(), ____.dup() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetConstructors", "(Ljava/lang/Class;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getConstructors", "()[Ljava/lang/reflect/Constructor;").__() }, // Methods. { ____.invokevirtual("java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;").__(), ____.dup_x2() .astore(LOCAL_VARIABLE_INDEX_1) .dup_x2() .astore(LOCAL_VARIABLE_INDEX_2) .dup_x2() .astore(LOCAL_VARIABLE_INDEX_3) .aload(LOCAL_VARIABLE_INDEX_3) .aload(LOCAL_VARIABLE_INDEX_2) .aload(LOCAL_VARIABLE_INDEX_1) .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetDeclaredMethod", "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;").__() }, { ____.invokevirtual("java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;").__(), ____.dup_x2() .astore(LOCAL_VARIABLE_INDEX_1) .dup_x2() .astore(LOCAL_VARIABLE_INDEX_2) .dup_x2() .astore(LOCAL_VARIABLE_INDEX_3) .aload(LOCAL_VARIABLE_INDEX_3) .aload(LOCAL_VARIABLE_INDEX_2) .aload(LOCAL_VARIABLE_INDEX_1) .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetMethod", "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;").__() }, { ____.invokevirtual("java/lang/Class", "getDeclaredMethods", "()[Ljava/lang/reflect/Method;").__(), ____.dup() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetDeclaredMethods", "(Ljava/lang/Class;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getDeclaredMethods", "()[Ljava/lang/reflect/Method;").__() }, { ____.invokevirtual("java/lang/Class", "getMethods", "()[Ljava/lang/reflect/Method;").__(), ____.dup() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetMethods", "(Ljava/lang/Class;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getMethods", "()[Ljava/lang/reflect/Method;").__() }, // Fields. { ____.invokevirtual("java/lang/Class", "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;").__(), ____.dup2() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetDeclaredField", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;").__() }, { ____.invokevirtual("java/lang/Class", "getField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;").__(), ____.dup2() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetField", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;").__() }, { ____.invokevirtual("java/lang/Class", "getDeclaredFields", "()[Ljava/lang/reflect/Field;").__(), ____.dup() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetDeclaredFields", "(Ljava/lang/Class;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getDeclaredFields", "()[Ljava/lang/reflect/Field;").__() }, { ____.invokevirtual("java/lang/Class", "getFields", "()[Ljava/lang/reflect/Field;").__(), ____.dup() .ldc_(CLASS_NAME) .invokestatic(LOGGER_CLASS_NAME, "checkGetFields", "(Ljava/lang/Class;Ljava/lang/String;)V") .invokevirtual("java/lang/Class", "getFields", "()[Ljava/lang/reflect/Field;").__() }, }; CONSTANTS = ____.constants(); } } ================================================ FILE: base/src/main/java/proguard/configuration/ConfigurationLoggingInstructionSequenceReplacer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.configuration; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.Constant; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.editor.InstructionSequenceReplacer; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.BranchTargetFinder; import proguard.classfile.util.ClassUtil; import proguard.classfile.util.InstructionSequenceMatcher; import static proguard.configuration.ConfigurationLoggingInstructionSequenceConstants.*; /** * This InstructionSequencesReplacer appends logging instructions to all * instructions calling reflection methods. * * @see InstructionSequenceReplacer * * @author Johan Leys */ public class ConfigurationLoggingInstructionSequenceReplacer extends InstructionSequenceReplacer { public ConfigurationLoggingInstructionSequenceReplacer(InstructionSequenceMatcher instructionSequenceMatcher, Constant[] patternConstants, Instruction[] patternInstructions, Constant[] replacementConstants, Instruction[] replacementInstructions, BranchTargetFinder branchTargetFinder, CodeAttributeEditor codeAttributeEditor, InstructionVisitor extraInstructionVisitor ) { super(instructionSequenceMatcher, patternConstants, patternInstructions, replacementConstants, replacementInstructions, branchTargetFinder, codeAttributeEditor, extraInstructionVisitor ); } public ConfigurationLoggingInstructionSequenceReplacer(Constant[] patternConstants, Instruction[] patternInstructions, Constant[] replacementConstants, Instruction[] replacementInstructions, BranchTargetFinder branchTargetFinder, CodeAttributeEditor codeAttributeEditor ) { super(patternConstants, patternInstructions, replacementConstants, replacementInstructions, branchTargetFinder, codeAttributeEditor ); } public ConfigurationLoggingInstructionSequenceReplacer(Constant[] patternConstants, Instruction[] patternInstructions, Constant[] replacementConstants, Instruction[] replacementInstructions, BranchTargetFinder branchTargetFinder, CodeAttributeEditor codeAttributeEditor, InstructionVisitor extraInstructionVisitor ) { super(patternConstants, patternInstructions, replacementConstants, replacementInstructions, branchTargetFinder, codeAttributeEditor, extraInstructionVisitor ); } @Override protected int matchedArgument(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int argument) { switch (argument) { case LOCAL_VARIABLE_INDEX_1: return codeAttribute.u2maxLocals; case LOCAL_VARIABLE_INDEX_2: return codeAttribute.u2maxLocals + 1; case LOCAL_VARIABLE_INDEX_3: return codeAttribute.u2maxLocals + 2; default: return super.matchedArgument(clazz, argument); } } @Override protected int matchedConstantIndex(ProgramClass programClass, int constantIndex) { switch (constantIndex) { case ConfigurationLoggingInstructionSequenceConstants.CLASS_NAME: return new ConstantPoolEditor(programClass) .addStringConstant(ClassUtil.externalClassName(programClass.getName()), programClass, null); default: return super.matchedConstantIndex(programClass, constantIndex); } } } ================================================ FILE: base/src/main/java/proguard/configuration/ConfigurationLoggingInstructionSequencesReplacer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.configuration; import proguard.classfile.constant.Constant; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.editor.InstructionSequenceReplacer; import proguard.classfile.editor.InstructionSequencesReplacer; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.instruction.visitor.MultiInstructionVisitor; import proguard.classfile.util.BranchTargetFinder; /** * This InstructionSequencesReplacer appends logging instructions to all * instructions calling reflection methods. * * @see InstructionSequencesReplacer * @see ConfigurationLoggingInstructionSequenceReplacer * * @author Johan Leys */ public class ConfigurationLoggingInstructionSequencesReplacer extends MultiInstructionVisitor implements InstructionVisitor { private static final int PATTERN_INDEX = 0; private static final int REPLACEMENT_INDEX = 1; /** * Creates a new ConfigurationLoggingInstructionSequencesReplacer. * * @param constants any constants referenced by the pattern * instructions and replacement instructions. * @param instructionSequences the instruction sequences to be replaced, * with subsequently the sequence pair index, * the patten/replacement index (0 or 1), * and the instruction index in the sequence. * @param branchTargetFinder a branch target finder that has been * initialized to indicate branch targets * in the visited code. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. */ public ConfigurationLoggingInstructionSequencesReplacer(Constant[] constants, Instruction[][][] instructionSequences, BranchTargetFinder branchTargetFinder, CodeAttributeEditor codeAttributeEditor) { this(constants, instructionSequences, branchTargetFinder, codeAttributeEditor, null); } /** * Creates a new ConfigurationLoggingInstructionSequencesReplacer. * * @param constants any constants referenced by the pattern * instructions and replacement instructions. * @param instructionSequences the instruction sequences to be replaced, * with subsequently the sequence pair index, * the patten/replacement index (0 or 1), * and the instruction index in the sequence. * @param branchTargetFinder a branch target finder that has been * initialized to indicate branch targets * in the visited code. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. * @param extraInstructionVisitor an optional extra visitor for all deleted * load instructions. */ public ConfigurationLoggingInstructionSequencesReplacer(Constant[] constants, Instruction[][][] instructionSequences, BranchTargetFinder branchTargetFinder, CodeAttributeEditor codeAttributeEditor, InstructionVisitor extraInstructionVisitor) { super(createInstructionSequenceReplacers(constants, instructionSequences, branchTargetFinder, codeAttributeEditor, extraInstructionVisitor)); } /** * Creates an array of InstructionSequenceReplacer instances. * * @param constants any constants referenced by the pattern * instructions and replacement instructions. * @param instructionSequences the instruction sequences to be replaced, * with subsequently the sequence pair index, * the from/to index (0 or 1), and the * instruction index in the sequence. * @param branchTargetFinder a branch target finder that has been * initialized to indicate branch targets * in the visited code. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. * @param extraInstructionVisitor an optional extra visitor for all deleted * load instructions. */ private static InstructionVisitor[] createInstructionSequenceReplacers(Constant[] constants, Instruction[][][] instructionSequences, BranchTargetFinder branchTargetFinder, CodeAttributeEditor codeAttributeEditor, InstructionVisitor extraInstructionVisitor) { InstructionVisitor[] instructionSequenceReplacers = new InstructionSequenceReplacer[instructionSequences.length]; for (int index = 0; index < instructionSequenceReplacers.length; index++) { Instruction[][] instructionSequencePair = instructionSequences[index]; instructionSequenceReplacers[index] = new ConfigurationLoggingInstructionSequenceReplacer(constants, instructionSequencePair[PATTERN_INDEX], constants, instructionSequencePair[REPLACEMENT_INDEX], branchTargetFinder, codeAttributeEditor, extraInstructionVisitor); } return instructionSequenceReplacers; } } ================================================ FILE: base/src/main/java/proguard/configuration/InitialStateInfo.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.configuration; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import proguard.resources.file.ResourceFilePool; import java.util.*; import static proguard.util.HashUtil.hashFnv1a32_UTF8; /** * Stores the initial state of a classpool and resource files including * class names, super class names and hashes of fields and methods; and * resource filenames. * * @author James Hamilton */ public class InitialStateInfo { private final List classNames = new ArrayList<>(); private final Map superClassNames = new HashMap<>(); private final Map> methodHashes = new HashMap<>(); private final Map> fieldHashes = new HashMap<>(); public InitialStateInfo(ClassPool classPool) { this.initialize(classPool); } /** * * @return The size of the class pool */ public int size() { return this.classNames.size(); } /** * * @return The class names */ public List classNames() { return classNames; } /** * Given a class name return it's super class name. * * @param className a class name * * @return super class name */ public String getSuperClassName(String className) { return this.superClassNames.get(className); } /** * Given a class name return a mapping of method -> original method hash * * @param className a class name * * @return map ProgramMethod -> hash(original method signature) */ public Map getMethodHashMap(String className) { return this.methodHashes.containsKey(className) ? this.methodHashes.get(className) : new HashMap<>(); } /** * Given a class name return a mapping of a field -> original field hash * * @param className a class name * * @return map ProgramField -> hash(original field name) */ public Map getFieldHashMap(String className) { return this.fieldHashes.containsKey(className) ? this.fieldHashes.get(className) : new HashMap<>(); } // Private utility methods. private void initialize(ClassPool classPool) { Iterator iterator = classPool.classNames(); while(iterator.hasNext()) { String className = iterator.next(); classNames.add(className); ProgramClass programClass = (ProgramClass)classPool.getClass(className); superClassNames.put(className, programClass.getSuperName()); for (ProgramMethod programMethod : programClass.methods) { if (!methodHashes.containsKey(className)) { methodHashes.put(className, new HashMap<>()); } methodHashes.get(className).put(programMethod, hash(programClass, programMethod)); } for (ProgramField programField : programClass.fields) { if (!fieldHashes.containsKey(className)) { fieldHashes.put(className, new HashMap<>()); } fieldHashes.get(className).put(programField, hash(programClass, programField)); } } } private static int hash(Clazz clazz, Method method) { return hashFnv1a32_UTF8(method.getName(clazz) + "(" + ClassUtil.externalMethodArguments(method.getDescriptor(clazz)) + ")") ; } private static int hash(Clazz clazz, Field field) { return hashFnv1a32_UTF8(field.getName(clazz)); } } ================================================ FILE: base/src/main/java/proguard/evaluation/AssumeClassSpecificationVisitorFactory.java ================================================ /* * 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.evaluation; import proguard.*; import proguard.classfile.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.visitor.*; import proguard.evaluation.value.*; import proguard.optimize.OptimizationInfoMemberFilter; import proguard.optimize.info.*; import proguard.util.WildcardManager; /** * This factory creates visitors to efficiently travel to specified classes and * class members and set specified values on them. * * @author Eric Lafortune */ public class AssumeClassSpecificationVisitorFactory extends ClassSpecificationVisitorFactory { private final ValueFactory valueFactory; public AssumeClassSpecificationVisitorFactory(ValueFactory valueFactory) { this.valueFactory = valueFactory; } // Overriding implementations for ClassSpecificationVisitorFactory. protected ClassVisitor createNonTestingClassVisitor(MemberSpecification memberSpecification, boolean isField, MemberVisitor memberVisitor, AttributeVisitor attributeVisitor, WildcardManager wildcardManager) { if (memberSpecification instanceof MemberValueSpecification) { // We can only know the value of this member specification at this // point. MemberValueSpecification memberValueSpecification = (MemberValueSpecification)memberSpecification; Number[] values = memberValueSpecification.values; if (values != null) { // Convert the Number array to a Value. Value value = value(values); // We're adding a member visitor to set the value. memberVisitor = new MultiMemberVisitor( memberVisitor, new OptimizationInfoMemberFilter( new MyMemberValueSetter(value))); } } return super.createNonTestingClassVisitor(memberSpecification, isField, memberVisitor, attributeVisitor, wildcardManager); } // Small utility methods. private Value value(Number[] values) { return values.length == 1 ? valueFactory.createIntegerValue(values[0].intValue()) : valueFactory.createIntegerValue(values[0].intValue(), values[1].intValue()); } /** * This MemberVisitor sets a given value on the optimization info of the * members that it visits. */ private static class MyMemberValueSetter implements MemberVisitor { private final Value value; public MyMemberValueSetter(Value value) { this.value = value; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { FieldOptimizationInfo .getFieldOptimizationInfo(programField) .setValue(value); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { MethodOptimizationInfo .getMethodOptimizationInfo(programMethod) .setReturnValue(value); } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { FieldOptimizationInfo .getFieldOptimizationInfo(libraryField) .setValue(value); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { MethodOptimizationInfo .getMethodOptimizationInfo(libraryMethod) .setReturnValue(value); } } } ================================================ FILE: base/src/main/java/proguard/fixer/kotlin/KotlinAnnotationCounter.java ================================================ /* * 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.fixer.kotlin; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.visitor.MemberVisitor; import proguard.shrink.SimpleUsageMarker; /** * @author james */ public class KotlinAnnotationCounter implements MemberVisitor, AttributeVisitor, AnnotationVisitor { private int count = 0; private int[] parameterAnnotationCount = null; private SimpleUsageMarker usageMarker; public KotlinAnnotationCounter(SimpleUsageMarker javaUsageMarker) { this.usageMarker = javaUsageMarker; } public KotlinAnnotationCounter() {} /** * Returns the number of annotations excluding parameter annotations. * * @return the number of annotations. */ public int getCount() { return count; } /** * Returns the number of annotations on param{index} or -1 if there is no parameter at that index. * * @param index parameter index. * @return the number of annotations or -1. */ public int getParameterAnnotationCount(int index) { return parameterAnnotationCount != null && parameterAnnotationCount.length > 0 && index < parameterAnnotationCount.length ? parameterAnnotationCount[index] : -1; } public KotlinAnnotationCounter reset() { this.count = 0; this.parameterAnnotationCount = null; return this; } // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) { member.accept(clazz, new AllAttributeVisitor(this)); } // Implementations for AttributeVisitor. @Override public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) { annotationsAttribute .annotationsAccept(clazz, new AnnotationTypeFilter("!" + KotlinConstants.TYPE_KOTLIN_METADATA, this)); } @Override public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) { this.parameterAnnotationCount = new int[parameterAnnotationsAttribute.u1parametersCount]; parameterAnnotationsAttribute .annotationsAccept(clazz, method, new AnnotationTypeFilter("!" + KotlinConstants.TYPE_KOTLIN_METADATA, this)); } @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) { } // Implementations for AnnotationVisitor. @Override public void visitAnnotation(Clazz clazz, Annotation annotation) { if (usageMarker == null || usageMarker.isUsed(annotation)) { this.count++; } } @Override public void visitAnnotation(Clazz clazz, Method method, int parameterIndex, Annotation annotation) { if (usageMarker == null || usageMarker.isUsed(annotation)) { this.parameterAnnotationCount[parameterIndex]++; } } } ================================================ FILE: base/src/main/java/proguard/fixer/kotlin/KotlinAnnotationFlagFixer.java ================================================ /* * 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.fixer.kotlin; import proguard.classfile.*; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.*; public class KotlinAnnotationFlagFixer implements KotlinMetadataVisitor, // Implementation interfaces. KotlinPropertyVisitor, KotlinFunctionVisitor, KotlinTypeAliasVisitor, KotlinTypeVisitor, KotlinConstructorVisitor, KotlinTypeParameterVisitor, KotlinValueParameterVisitor, KotlinVersionRequirementVisitor { private final KotlinAnnotationCounter annotationCounter; public KotlinAnnotationFlagFixer() { this.annotationCounter = new KotlinAnnotationCounter(); } @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} // Implementations for KotlinMetadataVisitor. @Override public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) { kotlinDeclarationContainerMetadata.propertiesAccept( clazz, this); kotlinDeclarationContainerMetadata.functionsAccept( clazz, this); kotlinDeclarationContainerMetadata.typeAliasesAccept( clazz, this); kotlinDeclarationContainerMetadata.delegatedPropertiesAccept(clazz, this); } @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinClassKindMetadata); kotlinClassKindMetadata.contextReceiverTypesAccept( clazz, this); kotlinClassKindMetadata.superTypesAccept( clazz, this); kotlinClassKindMetadata.typeParametersAccept( clazz, this); kotlinClassKindMetadata.versionRequirementAccept( clazz, this); kotlinClassKindMetadata.constructorsAccept( clazz, this); kotlinClassKindMetadata.inlineClassUnderlyingPropertyTypeAccept(clazz, this); kotlinClassKindMetadata.referencedClass.attributesAccept(annotationCounter.reset()); kotlinClassKindMetadata.flags.common.hasAnnotations = annotationCounter.getCount() > 0; } @Override public void visitKotlinFileFacadeMetadata(Clazz clazz, KotlinFileFacadeKindMetadata kotlinFileFacadeKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinFileFacadeKindMetadata); } @Override public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassKindMetadata kotlinSyntheticClassKindMetadata) { kotlinSyntheticClassKindMetadata.functionsAccept(clazz, this); } @Override public void visitKotlinMultiFileFacadeMetadata(Clazz clazz, KotlinMultiFileFacadeKindMetadata kotlinMultiFileFacadeKindMetadata) { } @Override public void visitKotlinMultiFilePartMetadata(Clazz clazz, KotlinMultiFilePartKindMetadata kotlinMultiFilePartKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinMultiFilePartKindMetadata); } // Implementations for KotlinPropertyVisitor. @Override public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata) { kotlinPropertyMetadata.versionRequirementAccept(clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.typeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.setterParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.contextReceiverTypesAccept(clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.receiverTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); if (kotlinPropertyMetadata.syntheticMethodForAnnotations != null) { kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations.accept( kotlinPropertyMetadata.referencedSyntheticMethodClass, annotationCounter.reset() ); kotlinPropertyMetadata.flags.common.hasAnnotations = annotationCounter.getCount() > 0; } else if (kotlinPropertyMetadata.referencedBackingField != null) { kotlinPropertyMetadata.referencedBackingField.accept(kotlinPropertyMetadata.referencedBackingFieldClass, annotationCounter); kotlinPropertyMetadata.flags.common.hasAnnotations = annotationCounter.getCount() > 0; } else { kotlinPropertyMetadata.flags.common.hasAnnotations = false; } if (kotlinPropertyMetadata.flags.hasGetter && kotlinPropertyMetadata.referencedGetterMethod != null) { kotlinPropertyMetadata.referencedGetterMethod.accept(clazz, annotationCounter.reset()); kotlinPropertyMetadata.getterFlags.common.hasAnnotations = annotationCounter.getCount() > 0; } if (kotlinPropertyMetadata.flags.hasSetter && kotlinPropertyMetadata.referencedSetterMethod != null) { kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, annotationCounter.reset()); kotlinPropertyMetadata.setterFlags.common.hasAnnotations = annotationCounter.getCount() > 0; } } // Implementations for KotlinFunctionVisitor. @Override public void visitAnyFunction(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { kotlinFunctionMetadata.contextReceiverTypesAccept(clazz, kotlinMetadata, this); kotlinFunctionMetadata.receiverTypeAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.typeParametersAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.valueParametersAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.returnTypeAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.referencedMethodAccept(annotationCounter.reset()); kotlinFunctionMetadata.flags.common.hasAnnotations = annotationCounter.getCount() != 0; } // Implementations for KotlinConstructorVisitor. @Override public void visitConstructor(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata, KotlinConstructorMetadata kotlinConstructorMetadata) { kotlinConstructorMetadata.valueParametersAccept( clazz, kotlinClassKindMetadata, this); kotlinConstructorMetadata.versionRequirementAccept(clazz, kotlinClassKindMetadata, this); if (kotlinClassKindMetadata.flags.isAnnotationClass) { //PROBBUG where are the annotations? kotlinConstructorMetadata.flags.common.hasAnnotations = false; } else { kotlinConstructorMetadata.referencedMethodAccept(clazz, annotationCounter.reset()); kotlinConstructorMetadata.flags.common.hasAnnotations = annotationCounter.getCount() != 0; } } // Implementations for KotlinTypeAliasVisitor. @Override public void visitTypeAlias(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinTypeAliasMetadata kotlinTypeAliasMetadata) { kotlinTypeAliasMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinTypeAliasMetadata.underlyingTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinTypeAliasMetadata.expandedTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinTypeAliasMetadata.versionRequirementAccept(clazz, kotlinDeclarationContainerMetadata, this); kotlinTypeAliasMetadata.flags.common.hasAnnotations = !kotlinTypeAliasMetadata.annotations.isEmpty(); } // Implementations for KotlinTypeVisitor. @Override public void visitAnyType(Clazz clazz, KotlinTypeMetadata kotlinTypeMetadata) { kotlinTypeMetadata.typeArgumentsAccept(clazz, this); kotlinTypeMetadata.upperBoundsAccept( clazz, this); kotlinTypeMetadata.abbreviationAccept( clazz, this); kotlinTypeMetadata.flags.common.hasAnnotations = !kotlinTypeMetadata.annotations.isEmpty(); } @Override public void visitFunctionReceiverType(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata, KotlinTypeMetadata kotlinTypeMetadata) { kotlinFunctionMetadata.referencedMethodAccept(this.annotationCounter.reset()); kotlinTypeMetadata.flags.common.hasAnnotations = annotationCounter.getParameterAnnotationCount(0) > 0; } // Implementations for KotlinTypeParameterVisitor. @Override public void visitAnyValueParameter(Clazz clazz, KotlinValueParameterMetadata kotlinValueParameterMetadata) {} @Override public void visitAnyTypeParameter(Clazz clazz, KotlinTypeParameterMetadata kotlinTypeParameterMetadata) { kotlinTypeParameterMetadata.upperBoundsAccept(clazz, this); kotlinTypeParameterMetadata.flags.common.hasAnnotations = !kotlinTypeParameterMetadata.annotations.isEmpty(); } // Implementations for KotlinValueParameterVisitor. @Override public void visitFunctionValParameter(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata, KotlinValueParameterMetadata kotlinValueParameterMetadata) { kotlinValueParameterMetadata.typeAccept(clazz, kotlinMetadata, kotlinFunctionMetadata, this); if (kotlinValueParameterMetadata.flags.common.hasAnnotations) { kotlinFunctionMetadata.referencedMethodAccept(annotationCounter.reset()); kotlinValueParameterMetadata.flags.common.hasAnnotations = annotationCounter.getParameterAnnotationCount(kotlinValueParameterMetadata.index) > 0; } } @Override public void visitConstructorValParameter(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata, KotlinConstructorMetadata kotlinConstructorMetadata, KotlinValueParameterMetadata kotlinValueParameterMetadata) { kotlinValueParameterMetadata.typeAccept(clazz, kotlinClassKindMetadata, kotlinConstructorMetadata, this); if (kotlinValueParameterMetadata.flags.common.hasAnnotations) { if (!kotlinClassKindMetadata.flags.isAnnotationClass) { kotlinConstructorMetadata.referencedMethodAccept(clazz, annotationCounter.reset()); kotlinValueParameterMetadata.flags.common.hasAnnotations = annotationCounter.getParameterAnnotationCount(kotlinValueParameterMetadata.index) > 0; } } } @Override public void visitPropertyValParameter(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata, KotlinValueParameterMetadata kotlinValueParameterMetadata) { kotlinValueParameterMetadata.typeAccept(clazz, kotlinDeclarationContainerMetadata, kotlinPropertyMetadata, this); if (kotlinValueParameterMetadata.flags.common.hasAnnotations) { kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, annotationCounter.reset()); kotlinValueParameterMetadata.flags.common.hasAnnotations = annotationCounter.getParameterAnnotationCount(kotlinValueParameterMetadata.index) > 0; } } @Override public void visitAnyVersionRequirement(Clazz clazz, KotlinVersionRequirementMetadata kotlinVersionRequirementMetadata) {} } ================================================ FILE: base/src/main/java/proguard/io/ClassMapDataEntryReplacer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.io; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.*; import proguard.configuration.InitialStateInfo; import proguard.util.ProcessingFlags; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; import static proguard.configuration.ConfigurationLogger.*; /** * This DataEntryReader writes a class mapping to each received data * entry, used for debugging of the configuration. * * @author Johan Leys */ public class ClassMapDataEntryReplacer implements DataEntryReader { private final ClassPool programClassPool; private final InitialStateInfo initialStateInfo; private final DataEntryWriter dataEntryWriter; private DataOutputStream dataOutputStream; public ClassMapDataEntryReplacer(ClassPool programClassPool, InitialStateInfo initialStateInfo, DataEntryWriter dataEntryWriter) { this.programClassPool = programClassPool; this.initialStateInfo = initialStateInfo; this.dataEntryWriter = dataEntryWriter; } // Implementations for DataEntryReader. @Override public void read(DataEntry dataEntry) throws IOException { OutputStream outputStream = dataEntryWriter.createOutputStream(dataEntry); if (outputStream != null) { dataOutputStream = new DataOutputStream(outputStream); try { dataOutputStream.writeInt(initialStateInfo.size()); writeClassMap(); } finally { dataOutputStream.close(); } } } // Private utility methods. private void writeClassMap() throws IOException { for (String className : initialStateInfo.classNames()) { ProgramClass clazz = (ProgramClass)programClassPool.getClass(className); dataOutputStream.writeUTF(ClassUtil.externalClassName(className)); if (clazz == null) { // The class is no longer in the classpool or encrypted class pool so it must have been shrunk. // obfuscated name (original name, because it was removed from class pool so not obfuscated) dataOutputStream.writeUTF(ClassUtil.externalClassName(className)); dataOutputStream.writeUTF(ClassUtil.externalClassName(initialStateInfo.getSuperClassName(className))); // flag dataOutputStream.writeShort(CLASS_SHRUNK); writeMembers(initialStateInfo.getFieldHashMap(className), Collections.emptyList()); writeMembers(initialStateInfo.getMethodHashMap(className), Collections.emptyList()); } else { // obfuscated name dataOutputStream.writeUTF(ClassUtil.externalClassName(clazz.getName())); dataOutputStream.writeUTF(ClassUtil.externalClassName(clazz.getSuperName())); dataOutputStream.writeShort( (isKept(clazz.processingFlags) ? CLASS_KEPT : 0) | (allDeclaredFieldsKept(clazz) ? ALL_DECLARED_FIELDS_KEPT : 0) | (allPublicFieldsKept(clazz) ? ALL_PUBLIC_FIELDS_KEPT : 0) | (allDeclaredConstructorsKept(clazz) ? ALL_DECLARED_CONSTRUCTORS_KEPT : 0) | (allPublicConstructorsKept(clazz) ? ALL_PUBLIC_CONSTRUCTORS_KEPT : 0) | (allDeclaredMethodsKept(clazz) ? ALL_DECLARED_METHODS_KEPT : 0) | (allPublicMethodsKept(clazz) ? ALL_PUBLIC_METHODS_KEPT : 0) ); writeMembers(initialStateInfo.getFieldHashMap(className), Arrays.asList(clazz.fields)); writeMembers(initialStateInfo.getMethodHashMap(className), Arrays.asList(clazz.methods)); } } } private void writeMembers(Map originalMemberHashes, Collection currentMembers) throws IOException { dataOutputStream.writeShort(originalMemberHashes.size()); for (Map.Entry entry : originalMemberHashes.entrySet()) { dataOutputStream.writeInt(entry.getValue()); dataOutputStream.writeByte((currentMembers.contains(entry.getKey()) ? 0 : MEMBER_SHRUNK) | (isKept(entry.getKey().getProcessingFlags()) ? MEMBER_KEPT : 0)); } } // Small utility methods. static boolean isKept(int processingFlags) { return (processingFlags & ProcessingFlags.DONT_OBFUSCATE) != 0 && (processingFlags & ProcessingFlags.DONT_SHRINK) != 0; } /** * Returns whether all fields of the class (not including the fields of super classes) are kept. */ private static boolean allDeclaredFieldsKept(Clazz clazz) { if ((clazz.getProcessingFlags() & ProcessingFlags.REMOVED_FIELDS) != 0) { return false; } MemberCounter unkeptCounter = new MemberCounter(); clazz.fieldsAccept( new MemberProcessingFlagFilter(0, ProcessingFlags.INJECTED, new MemberProcessingFlagFilter(ProcessingFlags.DONT_SHRINK | ProcessingFlags.DONT_OBFUSCATE, 0, null, unkeptCounter))); return unkeptCounter.getCount() == 0; } /** * Returns whether all public fields of the class, including the public fields of super classes, are kept. */ private static boolean allPublicFieldsKept(Clazz clazz) { ClassCounter removedCounter = new ClassCounter(); MemberCounter unkeptCounter = new MemberCounter(); clazz.hierarchyAccept(true, true, false, false, new ProgramClassFilter( new MultiClassVisitor( // Check for removed public fields. new ClassProcessingFlagFilter(ProcessingFlags.REMOVED_PUBLIC_FIELDS, 0, removedCounter), // Check for unkept public fields. new AllFieldVisitor( new MemberAccessFilter(AccessConstants.PUBLIC, 0, new MemberProcessingFlagFilter(0, ProcessingFlags.INJECTED, new MemberProcessingFlagFilter(ProcessingFlags.DONT_SHRINK | ProcessingFlags.DONT_OBFUSCATE, 0, null, unkeptCounter)))) ))); return removedCounter.getCount() == 0 && unkeptCounter.getCount() == 0; } /** * Returns whether all constructors of the class (not including the constructors of super classes) are kept. */ private static boolean allDeclaredConstructorsKept(Clazz clazz) { if ((clazz.getProcessingFlags() & ProcessingFlags.REMOVED_CONSTRUCTORS) != 0) { return false; } MemberCounter unkeptCounter = new MemberCounter(); clazz.methodsAccept( new ConstructorMethodFilter( new MemberProcessingFlagFilter(0, ProcessingFlags.INJECTED, new MemberProcessingFlagFilter(ProcessingFlags.DONT_SHRINK | ProcessingFlags.DONT_OBFUSCATE, 0, null, unkeptCounter)))); return unkeptCounter.getCount() == 0; } /** * Returns whether all public constructors of the class (not including the public constructors of super classes) * are kept. */ private static boolean allPublicConstructorsKept(Clazz clazz) { if ((clazz.getProcessingFlags() & ProcessingFlags.REMOVED_PUBLIC_CONSTRUCTORS) != 0) { return false; } MemberCounter unkeptCounter = new MemberCounter(); clazz.hierarchyAccept(true, true, false, false, new ProgramClassFilter( new AllMethodVisitor( new ConstructorMethodFilter( new MemberAccessFilter(AccessConstants.PUBLIC, 0, new MemberProcessingFlagFilter(0, ProcessingFlags.INJECTED, new MemberProcessingFlagFilter(ProcessingFlags.DONT_SHRINK | ProcessingFlags.DONT_OBFUSCATE, 0, null, unkeptCounter))))))); return unkeptCounter.getCount() == 0; } /** * Returns whether all methods of the class (not including the methods of super classes) are kept. */ private static boolean allDeclaredMethodsKept(Clazz clazz) { if ((clazz.getProcessingFlags() & ProcessingFlags.REMOVED_METHODS) != 0) { return false; } MemberCounter unkeptCounter = new MemberCounter(); clazz.methodsAccept( new InitializerMethodFilter( null, new MemberProcessingFlagFilter(0, ProcessingFlags.INJECTED, new MemberProcessingFlagFilter(ProcessingFlags.DONT_SHRINK | ProcessingFlags.DONT_OBFUSCATE, 0, null, unkeptCounter)))); return unkeptCounter.getCount() == 0; } /** * Returns whether all public methods of the class, including the public methods of super classes, are kept. */ private static boolean allPublicMethodsKept(Clazz clazz) { ClassCounter removedCounter = new ClassCounter(); MemberCounter unkeptCounter = new MemberCounter(); clazz.hierarchyAccept(true, true, false, false, new ProgramClassFilter( new MultiClassVisitor( // Check for removed public methods. new ClassProcessingFlagFilter(ProcessingFlags.REMOVED_PUBLIC_METHODS, 0, removedCounter), // Check for unkept public methods. new AllMethodVisitor( new InitializerMethodFilter( null, new MemberAccessFilter(AccessConstants.PUBLIC, 0, new MemberProcessingFlagFilter(0, ProcessingFlags.INJECTED, new MemberProcessingFlagFilter(ProcessingFlags.DONT_SHRINK | ProcessingFlags.DONT_OBFUSCATE, 0, null, unkeptCounter))))) ))); return removedCounter.getCount() == 0 && unkeptCounter.getCount() == 0; } } ================================================ FILE: base/src/main/java/proguard/io/ExtraDataEntryNameMap.java ================================================ /* * 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.io; import proguard.classfile.*; import proguard.util.MultiValueMap; import java.util.*; import static proguard.classfile.util.ClassUtil.internalClassName; /** * Map keeping track of all data entries that have associated extra data entries with them. * * It also supports extra data entries that are not attached to a particular data entry: these entries should always * be included in the output, independent of the contents of the output. * * @author Johan Leys */ public class ExtraDataEntryNameMap { private final MultiValueMap nameMap = new MultiValueMap(); /** * Clears all extra data entries. */ public void clear() { nameMap.clear(); } /** * Clears all extra class data entries. */ public void clearExtraClasses() { for (String dataEntryKey : nameMap.keySet()) { for (String dataEntryValue : new ArrayList(nameMap.get(dataEntryKey))) { if (dataEntryValue.endsWith(ClassConstants.CLASS_FILE_EXTENSION)) { nameMap.remove(dataEntryKey, dataEntryValue); } } } } /** * Adds an extra data entry that is not linked to a particular data entry. */ public void addExtraDataEntry(String extraDataEntryName) { nameMap.put(null, extraDataEntryName); } /** * Adds an extra class data entry that is not linked to a particular data entry. */ public void addExtraClass(String extraDataEntryName) { addExtraDataEntry(getClassDataEntryName(extraDataEntryName)); } /** * Adds an extra data entry to the given data entry. */ public void addExtraDataEntry(String keyDataEntryName, String extraDataEntryName) { nameMap.put(keyDataEntryName, extraDataEntryName); } /** * Registers an extra data entry to the given class data entry. */ public void addExtraDataEntryToClass(String keyClassName, String extraDataEntryName) { addExtraDataEntry(getClassDataEntryName(keyClassName), extraDataEntryName); } /** * Registers an extra class data entry to the given class data entry. */ public void addExtraClassToClass(Clazz keyClass, Clazz extraClass) { addExtraDataEntry(getClassDataEntryName(keyClass), getClassDataEntryName(extraClass)); } /** * Registers an extra class data entry to the given class data entry. */ public void addExtraClassToClass(Clazz keyClass, Class extraClass) { addExtraDataEntry(getClassDataEntryName(keyClass), getClassDataEntryName(extraClass)); } /** * Registers an extra class data entry to the given class data entry. */ public void addExtraClassToClass(Clazz keyClass, String extraClassName) { addExtraDataEntry(getClassDataEntryName(keyClass), getClassDataEntryName(extraClassName)); } /** * Registers an extra class data entry to the given class data entry. */ public void addExtraClassToClass(String keyClassName, Class extraClass) { addExtraDataEntry(getClassDataEntryName(keyClassName), getClassDataEntryName(extraClass)); } /** * Registers an extra class data entry to the given class data entry. */ public void addExtraClassToClass(String keyClassName, String extraClassName) { addExtraDataEntry(getClassDataEntryName(keyClassName), getClassDataEntryName(extraClassName)); } /** * Returns the names of all data entries that have extra data entries attached to them. */ public Set getKeyDataEntryNames() { return nameMap.keySet(); } /** * Returns the names of all extra data entries. */ public Set getAllExtraDataEntryNames() { return nameMap.getValues(); } /** * Returns the names of all extra data entries that are not attached to a particular data entry. */ public Set getDefaultExtraDataEntryNames() { return nameMap.get(null); } /** * Returns the names of all extra data entries attached to the given data entry. */ public Set getExtraDataEntryNames(String keyDataEntryName) { return nameMap.get(keyDataEntryName); } // Small utility methods. private String getClassDataEntryName(Clazz clazz) { return clazz.getName() + ClassConstants.CLASS_FILE_EXTENSION; } private String getClassDataEntryName(Class clazz) { return internalClassName(clazz.getName()) + ClassConstants.CLASS_FILE_EXTENSION; } public String getClassDataEntryName(String className) { return className + ClassConstants.CLASS_FILE_EXTENSION; } } ================================================ FILE: base/src/main/java/proguard/io/ExtraDataEntryReader.java ================================================ /* * 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.io; import java.io.IOException; import java.util.*; /** * This DataEntryReader delegates to another DataEntryReader, inserting * additional data entries that are attached to the read data entry. * * @author Eric Lafortune */ public class ExtraDataEntryReader implements DataEntryReader { private final ExtraDataEntryNameMap extraEntryNameMap; private final DataEntryReader dataEntryReader; private final DataEntryReader extraDataEntryReader; private final Set extraEntryNamesRead = new HashSet(); /** * Creates a new ExtraDataEntryReader that reads one given extra data entry * together with the first data entry that is read. * * @param extraEntryName the name of the extra data entry. * @param dataEntryReader the reader from which the entries are * read, including the extra data entry. */ public ExtraDataEntryReader(String extraEntryName, DataEntryReader dataEntryReader) { this(extraEntryName, dataEntryReader, dataEntryReader); } /** * Creates a new ExtraDataEntryReader that reads one given extra data entry * together with the first data entry that is read. * * @param extraEntryName the name of the extra data entry. * @param dataEntryReader the reader from which the entries are * read. * @param extraDataEntryReader the reader from which the extra data entry * will be read. */ public ExtraDataEntryReader(String extraEntryName, DataEntryReader dataEntryReader, DataEntryReader extraDataEntryReader) { this(new ExtraDataEntryNameMap(), dataEntryReader, extraDataEntryReader); extraEntryNameMap.addExtraDataEntry(extraEntryName); } /** * Creates a new ExtraDataEntryReader. * * @param extraEntryNameMap a map with data entry names and their * associated extra data entries. An extra * data entry that is associated with multiple * entries is only read once. * @param dataEntryReader the reader from which the entries are * read. */ public ExtraDataEntryReader(ExtraDataEntryNameMap extraEntryNameMap, DataEntryReader dataEntryReader) { this(extraEntryNameMap, dataEntryReader, dataEntryReader); } /** * Creates a new ExtraDataEntryReader. * * @param extraEntryNameMap a map with data entry names and their * associated extra data entries. An extra * data entry that is associated with multiple * entries is only read once. * @param dataEntryReader the reader from which the entries are * read. * @param extraDataEntryReader the reader from which the extra data entry * will be read. */ public ExtraDataEntryReader(ExtraDataEntryNameMap extraEntryNameMap, DataEntryReader dataEntryReader, DataEntryReader extraDataEntryReader) { this.extraEntryNameMap = extraEntryNameMap; this.dataEntryReader = dataEntryReader; this.extraDataEntryReader = extraDataEntryReader; } // Implementations for DataEntryReader. @Override public void read(DataEntry dataEntry) throws IOException { DataEntry parentEntry = dataEntry.getParent(); // Read all default extra entries. readExtraEntries(parentEntry, extraEntryNameMap.getDefaultExtraDataEntryNames()); // Read all extra entries attached to the current data entry. readExtraEntries(parentEntry, extraEntryNameMap.getExtraDataEntryNames(dataEntry.getName())); // Read the actual entry. dataEntryReader.read(dataEntry); } // Small utility methods. private void readExtraEntries(DataEntry parentEntry, Set extraEntryNames) throws IOException { if (extraEntryNames != null) { for (String extraEntryName : extraEntryNames) { // Haven't we read the extra entry yet? if (extraEntryNamesRead.add(extraEntryName)) { // Create a content-less extra entry. DataEntry extraEntry = new DummyDataEntry(parentEntry, extraEntryName, 0, false); // Read it. The reader is supposed to handle it properly. extraDataEntryReader.read(extraEntry); // Recursively read extra entries attached to this extra // entry. readExtraEntries(parentEntry, extraEntryNameMap.getExtraDataEntryNames(extraEntryName)); } } } } } ================================================ FILE: base/src/main/java/proguard/io/KotlinModuleRewriter.java ================================================ /* * 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.io; import kotlinx.metadata.KmAnnotation; import kotlinx.metadata.internal.metadata.jvm.deserialization.JvmMetadataVersion; import kotlinx.metadata.jvm.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import java.io.*; import java.nio.charset.Charset; import java.util.*; public class KotlinModuleRewriter extends DataEntryCopier { private static final Logger logger = LogManager.getLogger(KotlinModuleRewriter.class); private final ClassPool programClassPool; public KotlinModuleRewriter(ClassPool programClassPool, Charset charset, DataEntryWriter writer) { super(writer); this.programClassPool = programClassPool; } public void read(DataEntry dataEntry) throws IOException { super.read(dataEntry); } protected void copyData(InputStream inputStream, OutputStream outputStream) throws IOException { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); super.copyData(inputStream, byteStream); byte[] bytes = byteStream.toByteArray(); KotlinModuleMetadata kotlinModuleMetadata = KotlinModuleMetadata.read(bytes); KmModule kmModule = kotlinModuleMetadata.toKmModule(); ModuleTransformer moduleTransformer = new ModuleTransformer(); kmModule.accept(moduleTransformer); KotlinModuleMetadata.Writer writer = new KotlinModuleMetadata.Writer(); moduleTransformer.getResult().accept(writer); byte[] transformedBytes = writer.write(JvmMetadataVersion.INSTANCE.toArray()).getBytes(); outputStream.write(transformedBytes); } private class PackageInformation { private final String fqName; private final List fileFacades = new ArrayList<>(); private final Map multiFileClassParts = new HashMap<>(); private PackageInformation(String fqName) {this.fqName = fqName;} public void addToModule(KmModule out) { out.visitPackageParts(fqName, fileFacades, multiFileClassParts); } } private class ModuleTransformer extends KmModuleVisitor { private final Map newModuleInfo = new HashMap<>(); private PackageInformation getPackageInformation(String fqName) { if (newModuleInfo.containsKey(fqName)) { return newModuleInfo.get(fqName); } else { PackageInformation packageInformation = new PackageInformation(fqName); newModuleInfo.put(fqName, packageInformation); return packageInformation; } } private PackageInformation getPackageInformation(Clazz clazz) { return getPackageInformation(ClassUtil.externalPackageName(ClassUtil.externalClassName(clazz.getName()))); } public void visitPackageParts(String fqName, List fileFacades, Map multiFileClassParts) { for (String fileFacade : fileFacades) { Clazz newClass = programClassPool.getClass(fileFacade); if (newClass == null) { // This can occur in the case of wrong input modules. // For instance the kotlin.reflect module declares facades which are actually absent. continue; } getPackageInformation(newClass).fileFacades.add(newClass.getName()); } for (Map.Entry entry : multiFileClassParts.entrySet()) { Clazz keyClass = programClassPool.getClass(entry.getKey()); Clazz valueClass = programClassPool.getClass(entry.getValue()); if (keyClass == null || valueClass == null) { continue; } getPackageInformation(keyClass).multiFileClassParts.put(keyClass .getName(), valueClass.getName()); } } public KmModule getResult() { KmModule out = new KmModule(); for (PackageInformation packageInformation : newModuleInfo.values()) { packageInformation.addToModule(out); } return out; } public void visitAnnotation(KmAnnotation annotation) { logger.error("Cannot handle annotations yet"); } public void visitEnd() {} } } ================================================ FILE: base/src/main/java/proguard/io/UniqueDataEntryWriter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2020 Guardsquare NV */ package proguard.io; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.util.HashSet; import java.util.Set; /** * This DataEntryWriter delegates to a given {@link DataEntryWriter}, but a {@link DataEntry} can be written * at most one time (uniqueness is based on {@link DataEntry#getName()}. * * @author Johan Leys * @author James Hamilton */ public class UniqueDataEntryWriter implements DataEntryWriter { private final DataEntryWriter dataEntryWriter; private final Set written = new HashSet<>(); public UniqueDataEntryWriter(DataEntryWriter dataEntryWriter) { this.dataEntryWriter = dataEntryWriter; } // Implementations for DataEntryWriter. @Override public boolean createDirectory(DataEntry dataEntry) throws IOException { if (!written.contains(dataEntry.getName())) { try { return dataEntryWriter.createDirectory(dataEntry); } finally { written.add(dataEntry.getName()); } } return false; } @Override public boolean sameOutputStream(DataEntry dataEntry1, DataEntry dataEntry2) throws IOException { return dataEntryWriter.sameOutputStream(dataEntry1, dataEntry2); } @Override public OutputStream createOutputStream(DataEntry dataEntry) throws IOException { if (!written.contains(dataEntry.getName())) { try { return dataEntryWriter.createOutputStream(dataEntry); } finally { written.add(dataEntry.getName()); } } return null; } @Override public void close() throws IOException { dataEntryWriter.close(); } @Override public void println(PrintWriter pw, String prefix) { pw.println(prefix + "UniqueDataEntryWriter"); dataEntryWriter.println(pw, prefix + " "); } } ================================================ FILE: base/src/main/java/proguard/logging/Logging.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.logging; import org.apache.logging.log4j.*; import org.apache.logging.log4j.core.*; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; public class Logging { public static void configureVerbosity(boolean verbose) { LoggerContext ctx = (LoggerContext) LogManager.getContext(LogManager.class.getClassLoader(), false); Configuration config = ctx.getConfiguration(); LoggerConfig loggerConfig = config.getRootLogger(); loggerConfig.setLevel(verbose ? Level.INFO : Level.WARN); ctx.updateLoggers(); } } ================================================ FILE: base/src/main/java/proguard/mark/Marker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.mark; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.Configuration; import proguard.KeepClassSpecificationVisitorFactory; import proguard.classfile.AccessConstants; import proguard.classfile.ClassConstants; import proguard.classfile.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeNameFilter; import proguard.classfile.attribute.visitor.AttributeProcessingFlagFilter; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.kotlin.KotlinClassKindMetadata; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.kotlin.KotlinDeclarationContainerMetadata; import proguard.classfile.kotlin.KotlinFunctionMetadata; import proguard.classfile.kotlin.KotlinMetadata; import proguard.classfile.kotlin.KotlinSyntheticClassKindMetadata; import proguard.classfile.kotlin.visitor.KotlinFunctionToDefaultMethodVisitor; import proguard.classfile.kotlin.visitor.KotlinFunctionToMethodVisitor; import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor; import proguard.classfile.util.AllParameterVisitor; import proguard.classfile.visitor.AllMemberVisitor; import proguard.classfile.visitor.ClassAccessFilter; import proguard.classfile.visitor.ClassNameFilter; import proguard.classfile.visitor.ClassPoolVisitor; import proguard.classfile.visitor.ClassProcessingFlagFilter; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.MemberAccessFilter; import proguard.classfile.visitor.MemberDescriptorReferencedClassVisitor; import proguard.classfile.visitor.MemberNameFilter; import proguard.classfile.visitor.MemberProcessingFlagFilter; import proguard.classfile.visitor.MemberToClassVisitor; import proguard.classfile.visitor.MemberVisitor; import proguard.classfile.visitor.MultiClassPoolVisitor; import proguard.classfile.visitor.MultiClassVisitor; import proguard.classfile.visitor.MultiMemberVisitor; import proguard.classfile.visitor.NamedMethodVisitor; import proguard.pass.Pass; import proguard.util.ProcessingFlagSetter; import proguard.util.ProcessingFlags; import java.util.Arrays; import static proguard.util.ProcessingFlags.DONT_OBFUSCATE; import static proguard.util.ProcessingFlags.DONT_OPTIMIZE; import static proguard.util.ProcessingFlags.DONT_SHRINK; import static proguard.util.ProcessingFlags.INJECTED; /** * This pass translates the keep rules and other class specifications from the * configuration into processing flags on classes and class members. * * @author Johan Leys */ public class Marker implements Pass { private static final Logger logger = LogManager.getLogger(Marker.class); private final Configuration configuration; public Marker(Configuration configuration) { this.configuration = configuration; } /** * Marks classes and class members in the given class pools with * appropriate access flags, corresponding to the class specifications * in the configuration. */ @Override public void execute(AppView appView) { logger.info("Marking classes and class members to be kept..."); // Create a combined ClassPool visitor for marking classes. MultiClassPoolVisitor classPoolVisitor = new MultiClassPoolVisitor( createShrinkingMarker(configuration), createOptimizationMarker(configuration), createObfuscationMarker(configuration) ); // Mark the seeds. appView.programClassPool.accept(classPoolVisitor); appView.libraryClassPool.accept(classPoolVisitor); if (configuration.keepKotlinMetadata) { // Keep the Kotlin metadata annotation. ClassVisitor classVisitor = new ClassNameFilter(KotlinConstants.NAME_KOTLIN_METADATA, new MultiClassVisitor( new ProcessingFlagSetter(ProcessingFlags.DONT_SHRINK | ProcessingFlags.DONT_OPTIMIZE | ProcessingFlags.DONT_OBFUSCATE), new AllMemberVisitor( new ProcessingFlagSetter(ProcessingFlags.DONT_SHRINK | ProcessingFlags.DONT_OPTIMIZE | ProcessingFlags.DONT_OBFUSCATE)))); appView.programClassPool.classesAccept(classVisitor); appView.libraryClassPool.classesAccept(classVisitor); } // Mark members that can be safely used for generalization, // but only if optimization is enabled. if (configuration.optimize) { markSafeGeneralizationMembers(appView.programClassPool, appView.libraryClassPool); } if (configuration.keepKotlinMetadata) { disableOptimizationForKotlinFeatures(appView.programClassPool, appView.libraryClassPool); } } // Small utility methods. private ClassPoolVisitor createShrinkingMarker(Configuration configuration) { // Automatically mark the parameterless constructors of seed classes, // mainly for convenience and for backward compatibility. ProcessingFlagSetter marker = new ProcessingFlagSetter(ProcessingFlags.DONT_SHRINK); ClassVisitor classUsageMarker = new MultiClassVisitor( marker, new NamedMethodVisitor(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, marker)); // Create a visitor for marking the seeds. return new KeepClassSpecificationVisitorFactory(true, false, false) .createClassPoolVisitor(configuration.keep, classUsageMarker, marker); } private ClassPoolVisitor createOptimizationMarker(Configuration configuration) { ProcessingFlagSetter marker = new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE); // We'll also mark classes referenced from descriptors of class members // that can't be obfuscated. We're excluding enum classes at this time, // because they would always be marked, due to "SomeEnum[] values()". MemberVisitor descriptorClassMarker = new MemberDescriptorReferencedClassVisitor( new ClassAccessFilter(0, AccessConstants.ENUM, marker)); return new MultiClassPoolVisitor( // Create a visitor for marking the seeds. new KeepClassSpecificationVisitorFactory(false, true, false) .createClassPoolVisitor(configuration.keep, marker, // marking classes marker, // marking fields marker, // marking methods marker), // Create a visitor for marking the classes referenced from // descriptors of obfuscation class member seeds, to avoid // merging such classes, to avoid having to rename these class // members. new KeepClassSpecificationVisitorFactory(false, false, true) .createClassPoolVisitor(configuration.keep, null, descriptorClassMarker, // for fields descriptorClassMarker, // for methods null) ); } private ClassPoolVisitor createObfuscationMarker(Configuration configuration) { // We exclude injected classes from any user-defined pattern // that prevents obfuscation. ProcessingFlagSetter marker = new ProcessingFlagSetter(ProcessingFlags.DONT_OBFUSCATE); ClassVisitor classMarker = new ClassProcessingFlagFilter(0, ProcessingFlags.INJECTED, marker); MemberVisitor memberMarker = new MemberProcessingFlagFilter(0, INJECTED, marker); AttributeVisitor attributeMarker = new AttributeProcessingFlagFilter(0, INJECTED, marker); // Create a visitor for marking the seeds. return new KeepClassSpecificationVisitorFactory(false, false, true) .createClassPoolVisitor(configuration.keep, classMarker, memberMarker, memberMarker, attributeMarker); } private void markSafeGeneralizationMembers(ClassPool programClassPool, ClassPool libraryClassPool) { // Program classes are always available and safe to generalize/specialize from/to. ClassVisitor isClassAvailableMarker = new AllMemberVisitor( new ProcessingFlagSetter(ProcessingFlags.IS_CLASS_AVAILABLE)); programClassPool.classesAccept(isClassAvailableMarker); if (!configuration.optimizeConservatively) { libraryClassPool.classesAccept(isClassAvailableMarker); } // TODO: Mark library class members where appropriate in the conservative case. } /** * This method will disable optimization for all Kotlin components where required, * such as for $default methods. */ private void disableOptimizationForKotlinFeatures(ClassPool programClassPool, ClassPool libraryClassPool) { ClassVisitor classVisitor = new ReferencedKotlinMetadataVisitor(new KotlinDontOptimizeMarker()); programClassPool.classesAccept(classVisitor); libraryClassPool.classesAccept(classVisitor); } /** * This KotlinMetadataVisitor marks classes and members with DONT_OPTIMIZE if it is known * that optimizing them would break Kotlin functionality. */ public static class KotlinDontOptimizeMarker implements KotlinMetadataVisitor, KotlinFunctionVisitor { private static final MultiMemberVisitor MEMBER_AND_CLASS_MARKER = new MultiMemberVisitor( new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE), new MemberToClassVisitor( new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE))); // Implementations for KotlinMetadataVisitor. @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) { kotlinDeclarationContainerMetadata.functionsAccept(clazz, this); } @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinClassKindMetadata); // Mark the INSTANCE field for object classes. if (kotlinClassKindMetadata.flags.isObject) { clazz.fieldAccept(KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, null, MEMBER_AND_CLASS_MARKER); } // Mark default constructors - these are like $default methods, // they're marked by having an unused DefaultConstructorMarker parameter. clazz.methodsAccept( new MemberNameFilter(ClassConstants.METHOD_NAME_INIT, new AllParameterVisitor(false, (_clazz, member, parameterIndex, parameterCount, parameterOffset, parameterSize, parameterType, referencedClass) -> { if (parameterType.equals(KotlinConstants.TYPE_KOTLIN_DEFAULT_CONSTRUCTOR_MARKER)) { clazz.accept(new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE)); member.accept(_clazz, new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE)); } })) ); // Mark the field that stores the companion object. if (kotlinClassKindMetadata.companionObjectName != null) { kotlinClassKindMetadata.referencedCompanionFieldAccept(MEMBER_AND_CLASS_MARKER); } } @Override public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassKindMetadata kotlinSyntheticClassKindMetadata) { // Mark all the invoke() methods in lambda classes. if (kotlinSyntheticClassKindMetadata.flavor == KotlinSyntheticClassKindMetadata.Flavor.LAMBDA) { kotlinSyntheticClassKindMetadata .functionsAccept(clazz, new KotlinFunctionToMethodVisitor(MEMBER_AND_CLASS_MARKER)); } // Mark all the code inside the public methods of CallableReferences. if (clazz.extendsOrImplements(KotlinConstants.REFLECTION.CALLABLE_REFERENCE_CLASS_NAME)) { clazz.accept( new MultiClassVisitor( new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE), new AllMemberVisitor( new MemberAccessFilter(AccessConstants.PUBLIC, 0, new MultiMemberVisitor(new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE), new AllAttributeVisitor( new AttributeNameFilter(Attribute.CODE, new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE)))))))); } } // Implementations for KotlinFunctionVisitor. @Override public void visitAnyFunction(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) {} @Override public void visitFunction(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { // If there is a corresponding default method and the user specifically kept this method, mark it. if (hasAnyOf(kotlinFunctionMetadata.referencedMethod.getProcessingFlags(), DONT_OPTIMIZE, DONT_SHRINK, DONT_OBFUSCATE)) { kotlinFunctionMetadata.accept(clazz, kotlinDeclarationContainerMetadata, new KotlinFunctionToDefaultMethodVisitor(MEMBER_AND_CLASS_MARKER)); } // If a method with a default implementation is kept, don't optimize the default implementation. if (kotlinDeclarationContainerMetadata.k == KotlinConstants.METADATA_KIND_CLASS && ((KotlinClassKindMetadata)kotlinDeclarationContainerMetadata).flags.isInterface && !kotlinFunctionMetadata.flags.modality.isAbstract && hasAnyOf(kotlinFunctionMetadata.referencedMethod.getProcessingFlags(), DONT_OPTIMIZE, DONT_SHRINK, DONT_OBFUSCATE)) { /* TODO: use this when referencedDefaultImplementationMethodAccept is available in ProGuardCORE kotlinFunctionMetadata.referencedDefaultImplementationMethodAccept( new MultiMemberVisitor( new ProcessingFlagSetter(DONT_OPTIMIZE), new MemberToClassVisitor(new ProcessingFlagSetter(DONT_OPTIMIZE)) ) );*/ if (kotlinFunctionMetadata.referencedDefaultImplementationMethod != null && kotlinFunctionMetadata.referencedDefaultImplementationMethodClass != null) { kotlinFunctionMetadata.referencedDefaultImplementationMethod .accept(kotlinFunctionMetadata.referencedDefaultImplementationMethodClass, new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE)); kotlinFunctionMetadata.referencedDefaultImplementationMethodClass .accept(new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE)); } } } @Override public void visitSyntheticFunction(Clazz clazz, KotlinSyntheticClassKindMetadata kotlinSyntheticClassKindMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { // If there is a corresponding default method and the user specifically kept this method, mark it. if (hasAnyOf(kotlinFunctionMetadata.referencedMethod.getProcessingFlags(), DONT_OPTIMIZE, DONT_SHRINK, DONT_OBFUSCATE)) { kotlinFunctionMetadata.accept(clazz, kotlinSyntheticClassKindMetadata, new KotlinFunctionToDefaultMethodVisitor(MEMBER_AND_CLASS_MARKER)); } } } /** * Checks if any of the given integer bit flags are set in * the provided integer. */ private static boolean hasAnyOf(int value, int ... flag) { return (value & Arrays.stream(flag).reduce((a, b) -> a | b).orElse(0)) != 0; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/AttributeShrinker.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.visitor.*; import proguard.util.Processable; import java.util.Arrays; /** * This ClassVisitor removes attributes that are not marked as being used or * required. * * @see AttributeUsageMarker * * @author Eric Lafortune */ public class AttributeShrinker implements ClassVisitor, MemberVisitor, AttributeVisitor, RecordComponentInfoVisitor { // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Only change program classes - library classes are left unchanged. // Compact the array for class attributes. programClass.u2attributesCount = shrinkArray(programClass.attributes, programClass.u2attributesCount); // Compact the attributes in fields, methods, and class attributes, programClass.fieldsAccept(this); programClass.methodsAccept(this); programClass.attributesAccept(this); } // Implementations for MemberVisitor. public void visitProgramMember(ProgramClass programClass, ProgramMember programMember) { // Compact the attributes array. programMember.u2attributesCount = shrinkArray(programMember.attributes, programMember.u2attributesCount); // Compact any attributes of the remaining attributes. programMember.attributesAccept(programClass, this); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitRecordAttribute(Clazz clazz, RecordAttribute recordAttribute) { // Compact any attributes of the components. recordAttribute.componentsAccept(clazz, this); } public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Compact the attributes array. codeAttribute.u2attributesCount = shrinkArray(codeAttribute.attributes, codeAttribute.u2attributesCount); } // Implementations for RecordComponentInfoVisitor. public void visitRecordComponentInfo(Clazz clazz, RecordComponentInfo recordComponentInfo) { // Compact the attributes array. recordComponentInfo.u2attributesCount = shrinkArray(recordComponentInfo.attributes, recordComponentInfo.u2attributesCount); } // Small utility methods. /** * Removes all Processable objects that are not marked as being used * from the given array. * @return the new number of Processable objects. */ private static int shrinkArray(Processable[] array, int length) { int counter = 0; // Shift the used objects together. for (int index = 0; index < length; index++) { if (AttributeUsageMarker.isUsed(array[index])) { array[counter++] = array[index]; } } // Clear the remaining array elements. Arrays.fill(array, counter, length, null); return counter; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/AttributeUsageMarker.java ================================================ /* * 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.obfuscate; import proguard.classfile.Clazz; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.util.Processable; /** * This AttributeVisitor marks all attributes that it visits. * * @see AttributeShrinker * * @author Eric Lafortune */ public class AttributeUsageMarker implements AttributeVisitor { // A processing info flag to indicate the attribute is being used. private static final Object USED = new Object(); // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) { markAsUsed(attribute); } // Small utility methods. /** * Marks the given Processable as being used (or useful). * In this context, the Processable will be an Attribute object. */ private static void markAsUsed(Processable processable) { processable.setProcessingInfo(USED); } /** * Returns whether the given Processable has been marked as being used. * In this context, the Processable will be an Attribute object. */ public static boolean isUsed(Processable processable) { return processable.getProcessingInfo() == USED; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/ClassNameAdapterFunction.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import proguard.util.*; import java.util.*; /** * This StringFunction maps given names on obfuscated names, based on the * renamed classes in the given ClassPool. If no corresponding class is found, * it returns null. * * @author Johan Leys */ public class ClassNameAdapterFunction implements StringFunction { private final ClassPool classPool; private final Map packagePrefixMap; /** * Creates a new ClassNameAdapterFunction for the given class pool. */ public ClassNameAdapterFunction(ClassPool classPool) { this.classPool = classPool; this.packagePrefixMap = createPackagePrefixMap(classPool); } // Implementations for StringFunction. @Override public String transform(String fileName) { // Try to find a corresponding class name by removing increasingly // long suffixes. for (int suffixIndex = fileName.length() - 1; suffixIndex > 0; suffixIndex--) { char c = fileName.charAt(suffixIndex); if (!Character.isLetterOrDigit(c)) { // Chop off the suffix. String className = fileName.substring(0, suffixIndex); // Did we get to the package separator? if (c == TypeConstants.PACKAGE_SEPARATOR) { break; } // Is there a class corresponding to the data entry? Clazz clazz = classPool.getClass(className); if (clazz != null) { // Did the class get a new name? String newClassName = clazz.getName(); if (!className.equals(newClassName)) { return newClassName + fileName.substring(suffixIndex); } else { // Otherwise stop looking. return fileName; } } } } // Try to find a corresponding package name by increasingly removing // more subpackages. String packagePrefix = fileName; while (true) { // Chop off the class name or the last subpackage name. packagePrefix = ClassUtil.internalPackagePrefix(packagePrefix); // Stop when the package prefix is empty. // Don't match against the default package. if (packagePrefix.length() == 0) { break; } // Is there a package corresponding to the package prefix? String newPackagePrefix = (String)packagePrefixMap.get(packagePrefix); if (newPackagePrefix != null) { // Did the package get a new name? if (!packagePrefix.equals(newPackagePrefix)) { // Rename the resource. return newPackagePrefix + fileName.substring(packagePrefix.length()); } else { // Otherwise stop looking. return null; } } } return null; } /** * Creates a map of old package prefixes to new package prefixes, based on * the given class pool. */ private static Map createPackagePrefixMap(ClassPool classPool) { Map packagePrefixMap = new HashMap(); Iterator iterator = classPool.classNames(); while (iterator.hasNext()) { String className = (String)iterator.next(); String packagePrefix = ClassUtil.internalPackagePrefix(className); String mappedNewPackagePrefix = (String)packagePrefixMap.get(packagePrefix); if (mappedNewPackagePrefix == null || !mappedNewPackagePrefix.equals(packagePrefix)) { String newClassName = classPool.getClass(className).getName(); String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName); packagePrefixMap.put(packagePrefix, newPackagePrefix); } } return packagePrefixMap; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/ClassObfuscator.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.KotlinMetadataToClazzVisitor; import proguard.classfile.kotlin.visitor.filter.KotlinSyntheticClassKindFilter; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.util.*; import java.util.*; /** * This ClassVisitor comes up with obfuscated names for the * classes it visits, and for their class members. The actual renaming is * done afterward. * * @see proguard.obfuscate.ClassRenamer * * @author Eric Lafortune */ public class ClassObfuscator implements ClassVisitor, AttributeVisitor, InnerClassesInfoVisitor, ConstantVisitor { private final DictionaryNameFactory classNameFactory; private final DictionaryNameFactory packageNameFactory; private final boolean useMixedCaseClassNames; private final StringMatcher keepPackageNamesMatcher; private final String flattenPackageHierarchy; private final String repackageClasses; private final boolean allowAccessModification; private final boolean adaptKotlin; private final Set classNamesToAvoid = new HashSet(); // Map: [package prefix - new package prefix] private final Map packagePrefixMap = new HashMap(); // Map: [package prefix - package name factory] private final Map packagePrefixPackageNameFactoryMap = new HashMap(); // Map: [package prefix - numeric class name factory] private final Map packagePrefixClassNameFactoryMap = new HashMap(); // Map: [package prefix - numeric class name factory] private final Map packagePrefixNumericClassNameFactoryMap = new HashMap(); // Field acting as temporary variables and as return values for names // of outer classes and types of inner classes. private String newClassName; private boolean numericClassName; /** * Creates a new ClassObfuscator. * @param programClassPool the class pool in which class names * have to be unique. * @param libraryClassPool the class pool from which class names * have to be avoided. * @param classNameFactory the optional class obfuscation dictionary. * @param packageNameFactory the optional package obfuscation * dictionary. * @param useMixedCaseClassNames specifies whether obfuscated packages and * classes can get mixed-case names. * @param keepPackageNames the optional filter for which matching * package names are kept. * @param flattenPackageHierarchy the base package if the obfuscated package * hierarchy is to be flattened. * @param repackageClasses the base package if the obfuscated classes * are to be repackaged. * @param allowAccessModification specifies whether obfuscated classes can * be freely moved between packages. * @param adaptKotlin specifies whether Kotlin should be supported. */ public ClassObfuscator(ClassPool programClassPool, ClassPool libraryClassPool, DictionaryNameFactory classNameFactory, DictionaryNameFactory packageNameFactory, boolean useMixedCaseClassNames, List keepPackageNames, String flattenPackageHierarchy, String repackageClasses, boolean allowAccessModification, boolean adaptKotlin) { this.classNameFactory = classNameFactory; this.packageNameFactory = packageNameFactory; // First append the package separator if necessary. if (flattenPackageHierarchy != null && flattenPackageHierarchy.length() > 0) { flattenPackageHierarchy += TypeConstants.PACKAGE_SEPARATOR; } // First append the package separator if necessary. if (repackageClasses != null && repackageClasses.length() > 0) { repackageClasses += TypeConstants.PACKAGE_SEPARATOR; } this.useMixedCaseClassNames = useMixedCaseClassNames; this.keepPackageNamesMatcher = keepPackageNames == null ? null : new ListParser(new FileNameParser()).parse(keepPackageNames); this.flattenPackageHierarchy = flattenPackageHierarchy; this.repackageClasses = repackageClasses; this.allowAccessModification = allowAccessModification; this.adaptKotlin = adaptKotlin; // Map the root package onto the root package. packagePrefixMap.put("", ""); // Collect all names that have already been taken. programClassPool.classesAccept(new MyKeepCollector()); libraryClassPool.classesAccept(new MyKeepCollector()); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { // Does this class still need a new name? newClassName = newClassName(programClass); if (newClassName == null) { // Make sure the outer class has a name, if it exists. The name will // be stored as the new class name, as a side effect, so we'll be // able to use it as a prefix. programClass.attributesAccept(this); // Figure out a package prefix. The package prefix may actually be // the an outer class prefix, if any, or it may be the fixed base // package, if classes are to be repackaged. String newPackagePrefix = newClassName != null ? newClassName + TypeConstants.INNER_CLASS_SEPARATOR : newPackagePrefix(ClassUtil.internalPackagePrefix(programClass.getName())); // Come up with a new class name, numeric or ordinary. newClassName = newClassName != null && numericClassName ? generateUniqueNumericClassName(newPackagePrefix) : generateUniqueClassName(newPackagePrefix); setNewClassName(programClass, newClassName); } } @Override public void visitLibraryClass(LibraryClass libraryClass) { // This can happen for dubious input, if the outer class of a program // class is a library class, and its name is requested. newClassName = libraryClass.getName(); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { // Make sure the outer classes have a name, if they exist. innerClassesAttribute.innerClassEntriesAccept(clazz, this); } public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) { // Make sure the enclosing class has a name. enclosingMethodAttribute.referencedClassAccept(this); String innerClassName = clazz.getName(); String outerClassName = clazz.getClassName(enclosingMethodAttribute.u2classIndex); numericClassName = isNumericClassName(clazz, innerClassName, outerClassName); } // Implementations for InnerClassesInfoVisitor. public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { // Make sure the outer class has a name, if it exists. int innerClassIndex = innerClassesInfo.u2innerClassIndex; int outerClassIndex = innerClassesInfo.u2outerClassIndex; if (innerClassIndex != 0 && outerClassIndex != 0) { String innerClassName = clazz.getClassName(innerClassIndex); if (innerClassName.equals(clazz.getName())) { clazz.constantPoolEntryAccept(outerClassIndex, this); String outerClassName = clazz.getClassName(outerClassIndex); numericClassName = isNumericClassName(clazz, innerClassName, outerClassName); } } } /** * Returns whether the given class is a synthetic Kotlin lambda class. * We then know it's numeric. */ private boolean isSyntheticKotlinLambdaClass(Clazz innerClass) { // Kotlin synthetic lambda classes that were named based on the // location that they were inlined from may be named like // OuterClass$methodName$1 where $methodName$1 is the inner class // name. We can rename this class to OuterClass$1 but the default // code below doesn't detect it as numeric. ClassCounter counter = new ClassCounter(); innerClass.accept( new ReferencedKotlinMetadataVisitor( new KotlinSyntheticClassKindFilter( KotlinSyntheticClassKindFilter::isLambda, new KotlinMetadataToClazzVisitor(counter)))); return counter.getCount() == 1; } /** * Returns whether the given inner class name is a numeric name. */ private boolean isNumericClassName(Clazz innerClass, String innerClassName, String outerClassName) { if (this.adaptKotlin && isSyntheticKotlinLambdaClass(innerClass)) { return true; } int innerClassNameStart = outerClassName.length() + 1; int innerClassNameLength = innerClassName.length(); if (innerClassNameStart >= innerClassNameLength) { return false; } for (int index = innerClassNameStart; index < innerClassNameLength; index++) { if (!Character.isDigit(innerClassName.charAt(index))) { return false; } } return true; } // Implementations for ConstantVisitor. public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Make sure the outer class has a name. classConstant.referencedClassAccept(this); } /** * This ClassVisitor collects package names and class names that have to * be kept. */ private class MyKeepCollector implements ClassVisitor { @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Does the program class already have a new name? String newClassName = newClassName(programClass); if (newClassName != null) { // Remember not to use this name. classNamesToAvoid.add(mixedCaseClassName(newClassName)); // Are we not aggressively repackaging all obfuscated classes? if (repackageClasses == null || !allowAccessModification) { String className = programClass.getName(); // Keep the package name for all other classes in the same // package. Do this recursively if we're not doing any // repackaging. mapPackageName(className, newClassName, repackageClasses == null && flattenPackageHierarchy == null); } } } public void visitLibraryClass(LibraryClass libraryClass) { // Get the new name or the original name of the library class. String newClassName = newClassName(libraryClass); if (newClassName == null) { newClassName = libraryClass.getName(); } // Remember not to use this name. classNamesToAvoid.add(mixedCaseClassName(newClassName)); // Are we not aggressively repackaging all obfuscated classes? if (repackageClasses == null || !allowAccessModification) { String className = libraryClass.getName(); // Keep the package name for all other classes in the same // package. Do this recursively if we're not doing any // repackaging. mapPackageName(className, newClassName, repackageClasses == null && flattenPackageHierarchy == null); } } /** * Makes sure the package name of the given class will always be mapped * consistently with its new name. */ private void mapPackageName(String className, String newClassName, boolean recursively) { String packagePrefix = ClassUtil.internalPackagePrefix(className); String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName); // Put the mapping of this package prefix, and possibly of its // entire hierarchy, into the package prefix map. do { packagePrefixMap.put(packagePrefix, newPackagePrefix); if (!recursively) { break; } packagePrefix = ClassUtil.internalPackagePrefix(packagePrefix); newPackagePrefix = ClassUtil.internalPackagePrefix(newPackagePrefix); } while (packagePrefix.length() > 0 && newPackagePrefix.length() > 0); } } // Small utility methods. /** * Finds or creates the new package prefix for the given package. */ private String newPackagePrefix(String packagePrefix) { // Doesn't the package prefix have a new package prefix yet? String newPackagePrefix = (String)packagePrefixMap.get(packagePrefix); if (newPackagePrefix == null) { // Are we keeping the package name? if (keepPackageNamesMatcher != null && keepPackageNamesMatcher.matches(packagePrefix.length() > 0 ? packagePrefix.substring(0, packagePrefix.length()-1) : packagePrefix)) { return packagePrefix; } // Are we forcing a new package prefix? if (repackageClasses != null) { return repackageClasses; } // Are we forcing a new superpackage prefix? // Otherwise figure out the new superpackage prefix, recursively. String newSuperPackagePrefix = flattenPackageHierarchy != null ? flattenPackageHierarchy : newPackagePrefix(ClassUtil.internalPackagePrefix(packagePrefix)); // Come up with a new package prefix. newPackagePrefix = generateUniquePackagePrefix(newSuperPackagePrefix); // Remember to use this mapping in the future. packagePrefixMap.put(packagePrefix, newPackagePrefix); } return newPackagePrefix; } /** * Creates a new package prefix in the given new superpackage. */ private String generateUniquePackagePrefix(String newSuperPackagePrefix) { // Find the right name factory for this package. NameFactory packageNameFactory = (NameFactory)packagePrefixPackageNameFactoryMap.get(newSuperPackagePrefix); if (packageNameFactory == null) { // We haven't seen packages in this superpackage before. Create // a new name factory for them. packageNameFactory = new SimpleNameFactory(useMixedCaseClassNames); if (this.packageNameFactory != null) { packageNameFactory = new DictionaryNameFactory(this.packageNameFactory, packageNameFactory); } packagePrefixPackageNameFactoryMap.put(newSuperPackagePrefix, packageNameFactory); } return generateUniquePackagePrefix(newSuperPackagePrefix, packageNameFactory); } /** * Creates a new package prefix in the given new superpackage, with the * given package name factory. */ private String generateUniquePackagePrefix(String newSuperPackagePrefix, NameFactory packageNameFactory) { // Come up with package names until we get an original one. String newPackagePrefix; do { // Let the factory produce a package name. newPackagePrefix = newSuperPackagePrefix + packageNameFactory.nextName() + TypeConstants.PACKAGE_SEPARATOR; } while (packagePrefixMap.containsValue(newPackagePrefix)); return newPackagePrefix; } /** * Creates a new class name in the given new package. */ private String generateUniqueClassName(String newPackagePrefix) { // Find the right name factory for this package. NameFactory classNameFactory = (NameFactory)packagePrefixClassNameFactoryMap.get(newPackagePrefix); if (classNameFactory == null) { // We haven't seen classes in this package before. // Create a new name factory for them. classNameFactory = new SimpleNameFactory(useMixedCaseClassNames); if (this.classNameFactory != null) { classNameFactory = new DictionaryNameFactory(this.classNameFactory, classNameFactory); } packagePrefixClassNameFactoryMap.put(newPackagePrefix, classNameFactory); } return generateUniqueClassName(newPackagePrefix, classNameFactory); } /** * Creates a new class name in the given new package. */ private String generateUniqueNumericClassName(String newPackagePrefix) { // Find the right name factory for this package. NameFactory classNameFactory = (NameFactory)packagePrefixNumericClassNameFactoryMap.get(newPackagePrefix); if (classNameFactory == null) { // We haven't seen classes in this package before. // Create a new name factory for them. classNameFactory = new NumericNameFactory(); packagePrefixNumericClassNameFactoryMap.put(newPackagePrefix, classNameFactory); } return generateUniqueClassName(newPackagePrefix, classNameFactory); } /** * Creates a new class name in the given new package, with the given * class name factory. */ private String generateUniqueClassName(String newPackagePrefix, NameFactory classNameFactory) { // Come up with class names until we get an original one. String newClassName; String newMixedCaseClassName; do { // Let the factory produce a class name. newClassName = newPackagePrefix + classNameFactory.nextName(); newMixedCaseClassName = mixedCaseClassName(newClassName); } while (classNamesToAvoid.contains(newMixedCaseClassName)); // Explicitly make sure the name isn't used again if we have a // user-specified dictionary and we're not allowed to have mixed case // class names -- just to protect against problematic dictionaries. if (this.classNameFactory != null && !useMixedCaseClassNames) { classNamesToAvoid.add(newMixedCaseClassName); } return newClassName; } /** * Returns the given class name, unchanged if mixed-case class names are * allowed, or the lower-case version otherwise. */ private String mixedCaseClassName(String className) { return useMixedCaseClassNames ? className : className.toLowerCase(); } /** * Assigns a new name to the given class. * @param clazz the given class. * @param name the new name. */ public static void setNewClassName(Clazz clazz, String name) { clazz.setProcessingInfo(name); } /** * Returns whether the class name of the given class has changed. * * @param clazz the given class. * @return true if the class name is unchanged, false otherwise. */ public static boolean hasOriginalClassName(Clazz clazz) { return clazz.getName().equals(newClassName(clazz)); } /** * Retrieves the new name of the given class. * @param clazz the given class. * @return the class's new name, or null if it doesn't * have one yet. */ public static String newClassName(Clazz clazz) { Object processingInfo = clazz.getProcessingInfo(); return processingInfo instanceof String ? (String)processingInfo : null; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/ClassRenamer.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.visitor.*; /** * This ClassVisitor renames the class names and class member * names of the classes it visits, using names previously determined by the * obfuscator. * * @see ClassObfuscator * @see MemberObfuscator * * @author Eric Lafortune */ public class ClassRenamer implements ClassVisitor, MemberVisitor, ConstantVisitor { private final ClassVisitor extraClassVisitor; private final MemberVisitor extraMemberVisitor; /** * Creates a new ClassRenamer. */ public ClassRenamer() { this(null, null); } /** * Creates a new ClassRenamer. * * @param extraClassVisitor an optional extra visitor for classes that * have been renamed. * @param extraMemberVisitor an optional extra visitor for class members * that have been renamed. */ public ClassRenamer(ClassVisitor extraClassVisitor, MemberVisitor extraMemberVisitor) { this.extraClassVisitor = extraClassVisitor; this.extraMemberVisitor = extraMemberVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { // Rename this class. programClass.thisClassConstantAccept(this); // Rename the class members. programClass.fieldsAccept(this); programClass.methodsAccept(this); } @Override public void visitLibraryClass(LibraryClass libraryClass) { // Has the library class name changed? String name = libraryClass.getName(); String newName = ClassObfuscator.newClassName(libraryClass); if (newName != null && !newName.equals(name)) { libraryClass.thisClassName = newName; if (extraClassVisitor != null) { extraClassVisitor.visitLibraryClass(libraryClass); } } // Rename the class members. libraryClass.fieldsAccept(this); libraryClass.methodsAccept(this); } // Implementations for MemberVisitor. public void visitProgramMember(ProgramClass programClass, ProgramMember programMember) { // Has the class member name changed? String name = programMember.getName(programClass); String newName = MemberObfuscator.newMemberName(programMember); if (newName != null && !newName.equals(name)) { programMember.u2nameIndex = new ConstantPoolEditor(programClass).addUtf8Constant(newName); if (extraMemberVisitor != null) { programMember.accept(programClass, extraMemberVisitor); } } } public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember) { // Has the library member name changed? String name = libraryMember.getName(libraryClass); String newName = MemberObfuscator.newMemberName(libraryMember); if (newName != null && !newName.equals(name)) { libraryMember.name = newName; if (extraMemberVisitor != null) { libraryMember.accept(libraryClass, extraMemberVisitor); } } } // Implementations for ConstantVisitor. public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Update the Class entry if required. String name = clazz.getName(); String newName = ClassObfuscator.newClassName(clazz); if (newName != null && !newName.equals(name)) { // Refer to a new Utf8 entry. classConstant.u2nameIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newName); if (extraClassVisitor != null) { clazz.accept(extraClassVisitor); } } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/DictionaryNameFactory.java ================================================ /* * 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.obfuscate; import java.io.*; import java.net.URL; import java.util.*; /** * This NameFactory generates names that are read from a * specified input file. * Comments (everything starting with '#' on a single line) are ignored. * * @author Eric Lafortune */ public class DictionaryNameFactory implements NameFactory { private static final char COMMENT_CHARACTER = '#'; private final List names; private final NameFactory nameFactory; private int index = 0; /** * Creates a new DictionaryNameFactory. * @param url the URL from which the names can be read. * @param nameFactory the name factory from which names will be retrieved * if the list of read names has been exhausted. */ public DictionaryNameFactory(URL url, NameFactory nameFactory) throws IOException { this(url, true, nameFactory); } /** * Creates a new DictionaryNameFactory. * @param url the URL from which the names can be read. * @param validJavaIdentifiers specifies whether the produced names should * be valid Java identifiers. * @param nameFactory the name factory from which names will be * retrieved if the list of read names has been * exhausted. */ public DictionaryNameFactory(URL url, boolean validJavaIdentifiers, NameFactory nameFactory) throws IOException { this (new BufferedReader( new InputStreamReader( url.openStream(), "UTF-8")), validJavaIdentifiers, nameFactory); } /** * Creates a new DictionaryNameFactory. * @param file the file from which the names can be read. * @param nameFactory the name factory from which names will be retrieved * if the list of read names has been exhausted. */ public DictionaryNameFactory(File file, NameFactory nameFactory) throws IOException { this(file, true, nameFactory); } /** * Creates a new DictionaryNameFactory. * @param file the file from which the names can be read. * @param validJavaIdentifiers specifies whether the produced names should * be valid Java identifiers. * @param nameFactory the name factory from which names will be * retrieved if the list of read names has been * exhausted. */ public DictionaryNameFactory(File file, boolean validJavaIdentifiers, NameFactory nameFactory) throws IOException { this (new BufferedReader( new InputStreamReader( new FileInputStream(file), "UTF-8")), validJavaIdentifiers, nameFactory); } /** * Creates a new DictionaryNameFactory. * @param reader the reader from which the names can be read. The * reader is closed at the end. * @param nameFactory the name factory from which names will be retrieved * if the list of read names has been exhausted. */ public DictionaryNameFactory(Reader reader, NameFactory nameFactory) throws IOException { this(reader, true, nameFactory); } /** * Creates a new DictionaryNameFactory. * @param reader the reader from which the names can be read. * The reader is closed at the end. * @param validJavaIdentifiers specifies whether the produced names should * be valid Java identifiers. * @param nameFactory the name factory from which names will be * retrieved if the list of read names has been * exhausted. */ public DictionaryNameFactory(Reader reader, boolean validJavaIdentifiers, NameFactory nameFactory) throws IOException { this.names = new ArrayList(); this.nameFactory = nameFactory; try { StringBuffer buffer = new StringBuffer(); while (true) { // Read the next character. int c = reader.read(); // Is it a valid identifier character? if (c != -1 && (validJavaIdentifiers ? (buffer.length() == 0 ? Character.isJavaIdentifierStart((char)c) : Character.isJavaIdentifierPart((char)c)) : (c != '\n' && c != '\r' && c != COMMENT_CHARACTER))) { // Append it to the current identifier. buffer.append((char)c); } else { // Did we collect a new identifier? if (buffer.length() > 0) { // Add the completed name to the list of names, if it's // not in it yet. String name = buffer.toString(); if (!names.contains(name)) { names.add(name); } // Clear the buffer. buffer.setLength(0); } // Is this the beginning of a comment line? if (c == COMMENT_CHARACTER) { // Skip all characters till the end of the line. do { c = reader.read(); } while (c != -1 && c != '\n' && c != '\r'); } // Is this the end of the file? if (c == -1) { // Just return. return; } } } } finally { reader.close(); } } /** * Creates a new DictionaryNameFactory. * @param dictionaryNameFactory the dictionary name factory whose dictionary * will be used. * @param nameFactory the name factory from which names will be * retrieved if the list of read names has been * exhausted. */ public DictionaryNameFactory(DictionaryNameFactory dictionaryNameFactory, NameFactory nameFactory) { this.names = dictionaryNameFactory.names; this.nameFactory = nameFactory; } // Implementations for NameFactory. public void reset() { index = 0; nameFactory.reset(); } public String nextName() { String name; // Do we still have names? if (index < names.size()) { // Return the next name. name = (String)names.get(index++); } else { // Return the next different name from the other name factory. do { name = nameFactory.nextName(); } while (names.contains(name)); } return name; } public static void main(String[] args) { try { DictionaryNameFactory factory = new DictionaryNameFactory(new File(args[0]), new SimpleNameFactory()); // For debugging, we're always using UTF-8 instead of the default // character encoding, even for writing to the standard output. PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out, "UTF-8")); for (int counter = 0; counter < 50; counter++) { out.println("[" + factory.nextName() + "]"); } out.flush(); } catch (IOException ex) { ex.printStackTrace(); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/MapCleaner.java ================================================ /* * 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.obfuscate; import proguard.classfile.Clazz; import proguard.classfile.visitor.ClassVisitor; import java.util.Map; /** * This ClassVisitor clears a given map whenever it visits a class. * * @author Eric Lafortune */ public class MapCleaner implements ClassVisitor { private final Map map; /** * Creates a new MapCleaner. * @param map the map to be cleared. */ public MapCleaner(Map map) { this.map = map; } // Implementations for ClassVisitor. public void visitAnyClass(Clazz clazz) { map.clear(); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/MappingKeeper.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.util.ListUtil; /** * This MappingKeeper applies the mappings that it receives to its class pool, * so these mappings are ensured in a subsequent obfuscation step. * * @author Eric Lafortune */ public class MappingKeeper implements MappingProcessor { private final ClassPool classPool; private final WarningPrinter warningPrinter; // A field acting as a parameter. private Clazz clazz; /** * Creates a new MappingKeeper. * @param classPool the class pool in which class names and class * member names have to be mapped. * @param warningPrinter the optional warning printer to which warnings * can be printed. */ public MappingKeeper(ClassPool classPool, WarningPrinter warningPrinter) { this.classPool = classPool; this.warningPrinter = warningPrinter; } // Implementations for MappingProcessor. public boolean processClassMapping(String className, String newClassName) { // Find the class. String name = ClassUtil.internalClassName(className); clazz = classPool.getClass(name); if (clazz != null) { String newName = ClassUtil.internalClassName(newClassName); // Print out a warning if the mapping conflicts with a name that // was set before. if (warningPrinter != null) { String currentNewName = ClassObfuscator.newClassName(clazz); if (currentNewName != null && !currentNewName.equals(newName)) { warningPrinter.print(name, currentNewName, "Warning: " + className + " is not being kept as '" + ClassUtil.externalClassName(currentNewName) + "', but remapped to '" + newClassName + "'"); } } ClassObfuscator.setNewClassName(clazz, newName); // The class members have to be kept as well. return true; } return false; } public void processFieldMapping(String className, String fieldType, String fieldName, String newClassName, String newFieldName) { if (clazz != null && className.equals(newClassName)) { // Find the field. String name = fieldName; String descriptor = ClassUtil.internalType(fieldType); Field field = clazz.findField(name, descriptor); if (field != null) { // Print out a warning if the mapping conflicts with a name that // was set before. if (warningPrinter != null) { String currentNewName = MemberObfuscator.newMemberName(field); if (currentNewName != null && !currentNewName.equals(newFieldName)) { warningPrinter.print(ClassUtil.internalClassName(className), "Warning: " + className + ": field '" + fieldType + " " + fieldName + "' is not being kept as '" + currentNewName + "', but remapped to '" + newFieldName + "'"); } } // Make sure the mapping name will be kept. MemberObfuscator.setFixedNewMemberName(field, newFieldName); } } } public void processMethodMapping(String className, int firstLineNumber, int lastLineNumber, String methodReturnType, String methodName, String methodArguments, String newClassName, int newFirstLineNumber, int newLastLineNumber, String newMethodName) { if (clazz != null && className.equals(newClassName)) { // Find the method. String descriptor = ClassUtil.internalMethodDescriptor(methodReturnType, ListUtil.commaSeparatedList(methodArguments)); Method method = clazz.findMethod(methodName, descriptor); if (method != null) { // Print out a warning if the mapping conflicts with a name that // was set before. if (warningPrinter != null) { String currentNewName = MemberObfuscator.newMemberName(method); if (currentNewName != null && !currentNewName.equals(newMethodName)) { warningPrinter.print(ClassUtil.internalClassName(className), "Warning: " + className + ": method '" + methodReturnType + " " + methodName + JavaTypeConstants.METHOD_ARGUMENTS_OPEN + methodArguments + JavaTypeConstants.METHOD_ARGUMENTS_CLOSE + "' is not being kept as '" + currentNewName + "', but remapped to '" + newMethodName + "'"); } } // Make sure the mapping name will be kept. MemberObfuscator.setFixedNewMemberName(method, newMethodName); } } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/MappingPrinter.java ================================================ /* * 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.obfuscate; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.json.JSONException; import org.json.JSONObject; import proguard.classfile.Clazz; import proguard.classfile.JavaTypeConstants; import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramField; import proguard.classfile.ProgramMethod; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.LineNumberInfo; import proguard.classfile.attribute.LineNumberTableAttribute; import proguard.classfile.attribute.SourceFileAttribute; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.peephole.LineNumberLinearizer; import java.io.PrintWriter; import java.util.Stack; /** * This ClassVisitor prints out the renamed classes and class members with * their old names and new names. * * @see proguard.obfuscate.ClassRenamer * * @author Eric Lafortune */ public class MappingPrinter implements ClassVisitor, MemberVisitor, AttributeVisitor { private final PrintWriter pw; // A field serving as a return value for the visitor methods. private boolean printed; private static final Logger logger = LogManager.getLogger(MappingPrinter.class); /** * Creates a new MappingPrinter that prints to the given writer. * @param printWriter the writer to which to print. */ public MappingPrinter(PrintWriter printWriter) { this.pw = printWriter; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { String name = programClass.getName(); String newName = ClassObfuscator.newClassName(programClass); // Print out the class mapping. pw.println(ClassUtil.externalClassName(name) + " -> " + ClassUtil.externalClassName(newName) + ":"); SourceFileNamePrinter sourceFileNamePrinter = new SourceFileNamePrinter(pw); programClass.attributesAccept(sourceFileNamePrinter); // Print out the class members. programClass.fieldsAccept(this); programClass.methodsAccept(this); } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { String fieldName = programField.getName(programClass); String obfuscatedFieldName = MemberObfuscator.newMemberName(programField); if (obfuscatedFieldName == null) { obfuscatedFieldName = fieldName; } // Print out the field mapping. pw.println(" " + ClassUtil.externalType(programField.getDescriptor(programClass)) + " " + fieldName + " -> " + obfuscatedFieldName); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { String methodName = programMethod.getName(programClass); String obfuscatedMethodName = MemberObfuscator.newMemberName(programMethod); if (obfuscatedMethodName == null) { obfuscatedMethodName = methodName; } // Print out the method mapping, if it has line numbers. printed = false; programMethod.attributesAccept(programClass, this); // Otherwise print out the method mapping without line numbers. if (!printed) { pw.println(" " + ClassUtil.externalMethodReturnType(programMethod.getDescriptor(programClass)) + " " + methodName + JavaTypeConstants.METHOD_ARGUMENTS_OPEN + ClassUtil.externalMethodArguments(programMethod.getDescriptor(programClass)) + JavaTypeConstants.METHOD_ARGUMENTS_CLOSE + " -> " + obfuscatedMethodName); } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttribute.attributesAccept(clazz, method, this); } public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) { LineNumberInfo[] lineNumberTable = lineNumberTableAttribute.lineNumberTable; int lineNumberTableLength = lineNumberTableAttribute.u2lineNumberTableLength; String methodName = method.getName(clazz); String methodDescriptor = method.getDescriptor(clazz); String obfuscatedMethodName = MemberObfuscator.newMemberName(method); if (obfuscatedMethodName == null) { obfuscatedMethodName = methodName; } int lowestLineNumber = lineNumberTableAttribute.getLowestLineNumber(); int highestLineNumber = lineNumberTableAttribute.getHighestLineNumber(); // Does the method have any local line numbers at all? if (lineNumberTableAttribute.getSource(codeAttribute.u4codeLength) == null) { if (lowestLineNumber > 0) { // Print out the line number range of the method, // ignoring line numbers of any inlined methods. pw.println(" " + lowestLineNumber + ":" + highestLineNumber + ":" + ClassUtil.externalMethodReturnType(method.getDescriptor(clazz)) + " " + methodName + JavaTypeConstants.METHOD_ARGUMENTS_OPEN + ClassUtil.externalMethodArguments(method.getDescriptor(clazz)) + JavaTypeConstants.METHOD_ARGUMENTS_CLOSE + " -> " + obfuscatedMethodName); } else { // Print out the method mapping without line numbers. pw.println(" " + ClassUtil.externalMethodReturnType(method.getDescriptor(clazz)) + " " + methodName + JavaTypeConstants.METHOD_ARGUMENTS_OPEN + ClassUtil.externalMethodArguments(method.getDescriptor(clazz)) + JavaTypeConstants.METHOD_ARGUMENTS_CLOSE + " -> " + obfuscatedMethodName); } } // Print out the line numbers of any inlined methods and their // enclosing methods. Stack enclosingLineNumbers = new Stack<>(); LineNumberInfo previousInfo = new LineNumberInfo(0, 0); for (int index = 0; index < lineNumberTableLength; index++) { LineNumberInfo info = lineNumberTable[index]; // Are we entering or exiting an inlined block (or a merged block)? // We're testing on the identities out of convenience. String previousSource = previousInfo.getSource(); String source = info.getSource(); // Source can be null for injected code. if (source != null && !source.equals(previousSource)) { // Are we entering or exiting the block? int previousLineNumber = previousInfo.u2lineNumber; int lineNumber = info.u2lineNumber; if (lineNumber > previousLineNumber) { // We're entering an inlined block. // Accumulate its enclosing line numbers, so they can be // printed out for each inlined block. if (index > 0) { enclosingLineNumbers.push(previousInfo); } printInlinedMethodMapping(clazz.getName(), methodName, methodDescriptor, info, enclosingLineNumbers, obfuscatedMethodName); } // TODO: There appear to be cases where the stack is empty at this point, so we've added a check. else if (!enclosingLineNumbers.isEmpty()) { // We're exiting an inlined block. // Pop its enclosing line number. enclosingLineNumbers.pop(); } } else if (source == null && previousSource != null) { // TODO: There appear to be cases where the stack is empty at this point, so we've added a check. if (!enclosingLineNumbers.isEmpty()) { // When exiting a top-level inlined block, the source might be null. // See LineNumberLinearizer, line 185, exiting an inlined block. enclosingLineNumbers.pop(); } } previousInfo = info; } printed = true; } // Small utility methods. /** * Prints out the mapping of the specified inlined methods and its * enclosing methods. */ private void printInlinedMethodMapping(String className, String methodName, String methodDescriptor, LineNumberInfo inlinedInfo, Stack enclosingLineNumbers, String obfuscatedMethodName) { String source = inlinedInfo.getSource(); // Parse the information from the source string of the // inlined method. int separatorIndex1 = source.indexOf('.'); int separatorIndex2 = source.indexOf('(', separatorIndex1 + 1); int separatorIndex3 = source.indexOf(':', separatorIndex2 + 1); int separatorIndex4 = source.indexOf(':', separatorIndex3 + 1); String inlinedClassName = source.substring(0, separatorIndex1); String inlinedMethodName = source.substring(separatorIndex1 + 1, separatorIndex2); String inlinedMethodDescriptor = source.substring(separatorIndex2, separatorIndex3); String inlinedRange = source.substring(separatorIndex3); int startLineNumber = Integer.parseInt(source.substring(separatorIndex3 + 1, separatorIndex4)); int endLineNumber = Integer.parseInt(source.substring(separatorIndex4 + 1)); // Compute the shifted line number range. int shiftedStartLineNumber = inlinedInfo.u2lineNumber; int shiftedEndLineNumber = shiftedStartLineNumber + endLineNumber - startLineNumber; // Print out the line number range of the inlined method. pw.println(" " + shiftedStartLineNumber + ":" + shiftedEndLineNumber + ":" + ClassUtil.externalMethodReturnType(inlinedMethodDescriptor) + " " + (inlinedClassName.equals(className) ? "" : ClassUtil.externalClassName(inlinedClassName) + JavaTypeConstants.PACKAGE_SEPARATOR) + inlinedMethodName + JavaTypeConstants.METHOD_ARGUMENTS_OPEN + ClassUtil.externalMethodArguments(inlinedMethodDescriptor) + JavaTypeConstants.METHOD_ARGUMENTS_CLOSE + inlinedRange + " -> " + obfuscatedMethodName); // Print out the line numbers of the accumulated enclosing // methods. for (int enclosingIndex = enclosingLineNumbers.size()-1; enclosingIndex >= 0; enclosingIndex--) { LineNumberInfo enclosingInfo = enclosingLineNumbers.get(enclosingIndex); printEnclosingMethodMapping(className, methodName, methodDescriptor, shiftedStartLineNumber + ":" + shiftedEndLineNumber, enclosingInfo, obfuscatedMethodName); } } /** * Prints out the mapping of the specified enclosing method. */ private void printEnclosingMethodMapping(String className, String methodName, String methodDescriptor, String shiftedRange, LineNumberInfo enclosingInfo, String obfuscatedMethodName) { // Parse the information from the source string of the enclosing // method. String enclosingSource = enclosingInfo.getSource(); String enclosingClassName; String enclosingMethodName; String enclosingMethodDescriptor; int enclosingLineNumber; if (enclosingSource == null) { enclosingClassName = className; enclosingMethodName = methodName; enclosingMethodDescriptor = methodDescriptor; enclosingLineNumber = enclosingInfo.u2lineNumber; } else { int enclosingSeparatorIndex1 = enclosingSource.indexOf('.'); int enclosingSeparatorIndex2 = enclosingSource.indexOf('(', enclosingSeparatorIndex1 + 1); int enclosingSeparatorIndex3 = enclosingSource.indexOf(':', enclosingSeparatorIndex2 + 1); int enclosingSeparatorIndex4 = enclosingSource.indexOf(':', enclosingSeparatorIndex3 + 1); // We need the first line number to correct the shifted enclosing // line number back to its original range. int firstLineNumber = Integer.parseInt(enclosingSource.substring(enclosingSeparatorIndex3 + 1, enclosingSeparatorIndex4)); enclosingClassName = enclosingSource.substring(0, enclosingSeparatorIndex1); enclosingMethodName = enclosingSource.substring(enclosingSeparatorIndex1 + 1, enclosingSeparatorIndex2); enclosingMethodDescriptor = enclosingSource.substring(enclosingSeparatorIndex2, enclosingSeparatorIndex3); enclosingLineNumber = (enclosingInfo.u2lineNumber - firstLineNumber) % LineNumberLinearizer.SHIFT_ROUNDING + firstLineNumber; } // Print out the line number of the enclosing method. pw.println(" " + shiftedRange + ":" + ClassUtil.externalMethodReturnType(enclosingMethodDescriptor) + " " + (enclosingClassName.equals(className) ? "" : ClassUtil.externalClassName(enclosingClassName) + JavaTypeConstants.PACKAGE_SEPARATOR) + enclosingMethodName + JavaTypeConstants.METHOD_ARGUMENTS_OPEN + ClassUtil.externalMethodArguments(enclosingMethodDescriptor) + JavaTypeConstants.METHOD_ARGUMENTS_CLOSE + ":" + enclosingLineNumber + " -> " + obfuscatedMethodName); } private static class SourceFileNamePrinter implements AttributeVisitor { private final PrintWriter pw; public SourceFileNamePrinter(PrintWriter pw) { this.pw = pw; } @Override public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute) { String sourceFileName = clazz.getString(sourceFileAttribute.u2sourceFileIndex); JSONObject json = new JSONObject(); try { json.put("id", "sourceFile"); json.put("fileName", sourceFileName); pw.println("# " + json); } catch (JSONException e) { logger.info("Failed to convert source file name {} into JSON.", sourceFileName); } } @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) { } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/MappingProcessor.java ================================================ /* * 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.obfuscate; /** * This interface specifies methods to process name mappings between original * classes and their obfuscated versions. The mappings are typically read * from a mapping file. * * @see MappingReader * * @author Eric Lafortune */ public interface MappingProcessor { /** * Processes the given class name mapping. * * @param className the original class name. * @param newClassName the new class name. * @return whether the processor is interested in receiving mappings of the * class members of this class. */ public boolean processClassMapping(String className, String newClassName); /** * Processes the given field name mapping. * @param className the original class name. * @param fieldType the original external field type. * @param fieldName the original field name. * @param newClassName the new class name. * @param newFieldName the new field name. */ public void processFieldMapping(String className, String fieldType, String fieldName, String newClassName, String newFieldName); /** * Processes the given method name mapping. * @param className the original class name. * @param firstLineNumber the first line number of the method, or 0 if * it is not known. * @param lastLineNumber the last line number of the method, or 0 if * it is not known. * @param methodReturnType the original external method return type. * @param methodName the original external method name. * @param methodArguments the original external method arguments. * @param newClassName the new class name. * @param newFirstLineNumber the new first line number of the method, or 0 * if it is not known. * @param newLastLineNumber the new last line number of the method, or 0 * if it is not known. * @param newMethodName the new method name. */ public void processMethodMapping(String className, int firstLineNumber, int lastLineNumber, String methodReturnType, String methodName, String methodArguments, String newClassName, int newFirstLineNumber, int newLastLineNumber, String newMethodName); } ================================================ FILE: base/src/main/java/proguard/obfuscate/MappingReader.java ================================================ /* * 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.obfuscate; import java.io.*; /** * This class can parse mapping files and invoke a processor for each of the * mapping entries. * * @author Eric Lafortune */ public class MappingReader { private final File mappingFile; public MappingReader(File mappingFile) { this.mappingFile = mappingFile; } /** * Reads the mapping file, presenting all of the encountered mapping entries * to the given processor. */ public void pump(MappingProcessor mappingProcessor) throws IOException { LineNumberReader reader = new LineNumberReader( new BufferedReader( new InputStreamReader( new FileInputStream(mappingFile), "UTF-8"))); try { String className = null; // Read the subsequent class mappings and class member mappings. while (true) { String line = reader.readLine(); if (line == null) { break; } line = line.trim(); // Is it a non-comment line? if (!line.startsWith("#")) { // Is it a class mapping or a class member mapping? if (line.endsWith(":")) { // Process the class mapping and remember the class's // old name. className = processClassMapping(line, mappingProcessor); } else if (className != null) { // Process the class member mapping, in the context of // the current old class name. processClassMemberMapping(className, line, mappingProcessor); } } } } catch (IOException ex) { throw new IOException("Can't process mapping file (" + ex.getMessage() + ")"); } finally { try { reader.close(); } catch (IOException ex) { // This shouldn't happen. } } } /** * Parses the given line with a class mapping and processes the * results with the given mapping processor. Returns the old class name, * or null if any subsequent class member lines can be ignored. */ private String processClassMapping(String line, MappingProcessor mappingProcessor) { // See if we can parse "___ -> ___:", containing the original // class name and the new class name. int arrowIndex = line.indexOf("->"); if (arrowIndex < 0) { return null; } int colonIndex = line.indexOf(':', arrowIndex + 2); if (colonIndex < 0) { return null; } // Extract the elements. String className = line.substring(0, arrowIndex).trim(); String newClassName = line.substring(arrowIndex + 2, colonIndex).trim(); // Process this class name mapping. boolean interested = mappingProcessor.processClassMapping(className, newClassName); return interested ? className : null; } /** * Parses the given line with a class member mapping and processes the * results with the given mapping processor. */ private void processClassMemberMapping(String className, String line, MappingProcessor mappingProcessor) { // See if we can parse one of // ___ ___ -> ___ // ___:___:___ ___(___) -> ___ // ___:___:___ ___(___):___ -> ___ // ___:___:___ ___(___):___:___ -> ___ // containing the optional line numbers, the return type, the original // field/method name, optional arguments, the optional original line // numbers, and the new field/method name. The original field/method // name may contain an original class name "___.___". int colonIndex1 = line.indexOf(':'); int colonIndex2 = colonIndex1 < 0 ? -1 : line.indexOf(':', colonIndex1 + 1); int spaceIndex = line.indexOf(' ', colonIndex2 + 2); int argumentIndex1 = line.indexOf('(', spaceIndex + 1); int argumentIndex2 = argumentIndex1 < 0 ? -1 : line.indexOf(')', argumentIndex1 + 1); int colonIndex3 = argumentIndex2 < 0 ? -1 : line.indexOf(':', argumentIndex2 + 1); int colonIndex4 = colonIndex3 < 0 ? -1 : line.indexOf(':', colonIndex3 + 1); int arrowIndex = line.indexOf("->", (colonIndex4 >= 0 ? colonIndex4 : colonIndex3 >= 0 ? colonIndex3 : argumentIndex2 >= 0 ? argumentIndex2 : spaceIndex) + 1); if (spaceIndex < 0 || arrowIndex < 0) { return; } // Extract the elements. String type = line.substring(colonIndex2 + 1, spaceIndex).trim(); String name = line.substring(spaceIndex + 1, argumentIndex1 >= 0 ? argumentIndex1 : arrowIndex).trim(); String newName = line.substring(arrowIndex + 2).trim(); // Does the method name contain an explicit original class name? String newClassName = className; int dotIndex = name.lastIndexOf('.'); if (dotIndex >= 0) { className = name.substring(0, dotIndex); name = name.substring(dotIndex + 1); } // Process this class member mapping. if (type.length() > 0 && name.length() > 0 && newName.length() > 0) { // Is it a field or a method? if (argumentIndex2 < 0) { mappingProcessor.processFieldMapping(className, type, name, newClassName, newName); } else { int firstLineNumber = 0; int lastLineNumber = 0; int newFirstLineNumber = 0; int newLastLineNumber = 0; if (colonIndex2 >= 0) { firstLineNumber = newFirstLineNumber = Integer.parseInt(line.substring(0, colonIndex1).trim()); lastLineNumber = newLastLineNumber = Integer.parseInt(line.substring(colonIndex1 + 1, colonIndex2).trim()); } if (colonIndex3 >= 0) { firstLineNumber = Integer.parseInt(line.substring(colonIndex3 + 1, colonIndex4 > 0 ? colonIndex4 : arrowIndex).trim()); lastLineNumber = colonIndex4 < 0 ? firstLineNumber : Integer.parseInt(line.substring(colonIndex4 + 1, arrowIndex).trim()); } String arguments = line.substring(argumentIndex1 + 1, argumentIndex2).trim(); mappingProcessor.processMethodMapping(className, firstLineNumber, lastLineNumber, type, name, arguments, newClassName, newFirstLineNumber, newLastLineNumber, newName); } } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/MemberNameCleaner.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor clears the new names of the class members * that it visits. * * @see MemberObfuscator * * @author Eric Lafortune */ public class MemberNameCleaner implements MemberVisitor { // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { MemberObfuscator.setNewMemberName(programField, null); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { MemberObfuscator.setNewMemberName(programMethod, null); } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { MemberObfuscator.setNewMemberName(libraryField, null); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { MemberObfuscator.setNewMemberName(libraryMethod, null); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/MemberNameCollector.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import java.util.Map; /** * This MemberVisitor collects all new (obfuscation) names of the members * that it visits. * * @see MemberObfuscator * * @author Eric Lafortune */ public class MemberNameCollector implements MemberVisitor { private final boolean allowAggressiveOverloading; private final Map descriptorMap; /** * Creates a new MemberNameCollector. * @param allowAggressiveOverloading a flag that specifies whether class * members can be overloaded aggressively. * @param descriptorMap the map of descriptors to * [new name - old name] maps. */ public MemberNameCollector(boolean allowAggressiveOverloading, Map descriptorMap) { this.allowAggressiveOverloading = allowAggressiveOverloading; this.descriptorMap = descriptorMap; } // Implementations for MemberVisitor. public void visitAnyMember(Clazz clazz, Member member) { // Special cases: and are always kept unchanged. // We can ignore them here. String name = member.getName(clazz); if (ClassUtil.isInitializer(name)) { return; } // Get the member's new name. String newName = MemberObfuscator.newMemberName(member); // Remember it, if it has already been set. if (newName != null) { // Get the member's descriptor. String descriptor = member.getDescriptor(clazz); // Check whether we're allowed to do aggressive overloading if (!allowAggressiveOverloading) { // Trim the return argument from the descriptor if not. // Works for fields and methods alike. descriptor = descriptor.substring(0, descriptor.indexOf(')')+1); } // Put the [descriptor - new name] in the map, // creating a new [new name - old name] map if necessary. Map nameMap = MemberObfuscator.retrieveNameMap(descriptorMap, descriptor); // Isn't there another original name for this new name, or should // this original name get priority? String otherName = (String)nameMap.get(newName); if (otherName == null || MemberObfuscator.hasFixedNewMemberName(member)) { // Remember not to use the new name again in this name space. nameMap.put(newName, name); } } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/MemberNameConflictFixer.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import java.util.Map; /** * This MemberInfoVisitor solves obfuscation naming conflicts in all class * members that it visits. It avoids names from the given descriptor map, * delegating to the given obfuscator in order to get a new name if necessary. * * @author Eric Lafortune */ public class MemberNameConflictFixer implements MemberVisitor { private final boolean allowAggressiveOverloading; private final Map descriptorMap; private final WarningPrinter warningPrinter; private final MemberObfuscator memberObfuscator; /** * Creates a new MemberNameConflictFixer. * @param allowAggressiveOverloading a flag that specifies whether class * members can be overloaded aggressively. * @param descriptorMap the map of descriptors to * [new name - old name] maps. * @param warningPrinter an optional warning printer to which * warnings about conflicting name * mappings can be printed. * @param memberObfuscator the obfuscator that can assign new * names to members with conflicting * names. */ public MemberNameConflictFixer(boolean allowAggressiveOverloading, Map descriptorMap, WarningPrinter warningPrinter, MemberObfuscator memberObfuscator) { this.allowAggressiveOverloading = allowAggressiveOverloading; this.descriptorMap = descriptorMap; this.warningPrinter = warningPrinter; this.memberObfuscator = memberObfuscator; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { visitMember(programClass, programField, true); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Special cases: and are always kept unchanged. // We can ignore them here. String name = programMethod.getName(programClass); if (ClassUtil.isInitializer(name)) { return; } visitMember(programClass, programMethod, false); } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {} public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {} /** * Obfuscates the given class member. * @param clazz the class of the given member. * @param member the class member to be obfuscated. * @param isField specifies whether the class member is a field. */ private void visitMember(Clazz clazz, Member member, boolean isField) { // Get the member's name and descriptor. String name = member.getName(clazz); String descriptor = member.getDescriptor(clazz); // Check whether we're allowed to overload aggressively. if (!allowAggressiveOverloading) { // Trim the return argument from the descriptor if not. // Works for fields and methods alike. descriptor = descriptor.substring(0, descriptor.indexOf(')')+1); } // Get the name map. Map nameMap = MemberObfuscator.retrieveNameMap(descriptorMap, descriptor); // Get the member's new name. String newName = MemberObfuscator.newMemberName(member); // Get the expected old name for this new name. String previousName = (String)nameMap.get(newName); if (previousName != null && !name.equals(previousName)) { // There's a conflict! A member (with a given old name) in a // first namespace has received the same new name as this // member (with a different old name) in a second name space, // and now these two have to live together in this name space. if (MemberObfuscator.hasFixedNewMemberName(member) && warningPrinter != null) { descriptor = member.getDescriptor(clazz); warningPrinter.print(clazz.getName(), "Warning: " + ClassUtil.externalClassName(clazz.getName()) + (isField ? ": field '" + ClassUtil.externalFullFieldDescription(0, name, descriptor) : ": method '" + ClassUtil.externalFullMethodDescription(clazz.getName(), 0, name, descriptor)) + "' can't be mapped to '" + newName + "' because it would conflict with " + (isField ? "field '" : "method '" ) + previousName + "', which is already being mapped to '" + newName + "'"); } // Clear the conflicting name. MemberObfuscator.setNewMemberName(member, null); // Assign a new name. member.accept(clazz, memberObfuscator); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/MemberObfuscator.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import proguard.util.*; import java.util.*; /** * This MemberVisitor obfuscates all class members that it visits. * It uses names from the given name factory. At the same time, it avoids names * from the given descriptor map. *

* The class members must have been linked before applying this visitor. * * @see MethodLinker * * @author Eric Lafortune */ public class MemberObfuscator implements MemberVisitor { private final boolean allowAggressiveOverloading; private final NameFactory nameFactory; private final Map descriptorMap; /** * Creates a new MemberObfuscator. * @param allowAggressiveOverloading a flag that specifies whether class * members can be overloaded aggressively. * @param nameFactory the factory that can produce * obfuscated member names. * @param descriptorMap the map of descriptors to * [new name - old name] maps. */ public MemberObfuscator(boolean allowAggressiveOverloading, NameFactory nameFactory, Map descriptorMap) { this.allowAggressiveOverloading = allowAggressiveOverloading; this.nameFactory = nameFactory; this.descriptorMap = descriptorMap; } // Implementations for MemberVisitor. public void visitAnyMember(Clazz clazz, Member member) { // Special cases: and are always kept unchanged. // We can ignore them here. String name = member.getName(clazz); if (ClassUtil.isInitializer(name)) { return; } // Get the member's descriptor. String descriptor = member.getDescriptor(clazz); // Check whether we're allowed to overload aggressively. if (!allowAggressiveOverloading) { // Trim the return argument from the descriptor if not. // Works for fields and methods alike. descriptor = descriptor.substring(0, descriptor.indexOf(')')+1); } // Get the name map, creating a new one if necessary. Map nameMap = retrieveNameMap(descriptorMap, descriptor); // Get the member's new name. String newName = newMemberName(member); // Assign a new one, if necessary. if (newName == null) { // Find an acceptable new name. nameFactory.reset(); do { newName = nameFactory.nextName(); } while (nameMap.containsKey(newName)); // Remember not to use the new name again in this name space. nameMap.put(newName, name); // Assign the new name. setNewMemberName(member, newName); } } // Small utility methods. /** * Gets the name map, based on the given map and a given descriptor. * A new empty map is created if necessary. * @param descriptorMap the map of descriptors to [new name - old name] maps. * @param descriptor the class member descriptor. * @return the corresponding name map. */ static Map retrieveNameMap(Map descriptorMap, String descriptor) { // See if we can find the nested map with this descriptor key. Map nameMap = (Map)descriptorMap.get(descriptor); // Create a new one if not. if (nameMap == null) { nameMap = new HashMap(); descriptorMap.put(descriptor, nameMap); } return nameMap; } /** * Assigns a fixed new name to the given class member. * @param member the class member. * @param name the new name. */ public static void setFixedNewMemberName(Member member, String name) { Processable lastProcessable = MethodLinker.lastProcessable(member); if (!(lastProcessable instanceof LibraryMember) && !(lastProcessable instanceof MyFixedName)) { lastProcessable.setProcessingInfo(new MyFixedName(name)); } else { lastProcessable.setProcessingInfo(name); } } /** * Assigns a new name to the given class member. * @param member the class member. * @param name the new name. */ public static void setNewMemberName(Member member, String name) { MethodLinker.lastProcessable(member).setProcessingInfo(name); } /** * Returns whether the new name of the given class member is fixed. * @param member the class member. * @return whether its new name is fixed. */ static boolean hasFixedNewMemberName(Member member) { Processable lastProcessable = MethodLinker.lastProcessable(member); return lastProcessable instanceof LibraryMember || lastProcessable instanceof MyFixedName; } /** * Retrieves the new name of the given class member. * @param member the class member. * @return the class member's new name, or null if it doesn't * have one yet. */ public static String newMemberName(Member member) { return (String)MethodLinker.lastProcessable(member).getProcessingInfo(); } /** * This Processable can be used to wrap a name string, to indicate that * the name is fixed. */ private static class MyFixedName extends SimpleProcessable { public MyFixedName(String newName) { super(0, newName); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/MemberSpecialNameFilter.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor delegates its visits to another given * MemberVisitor, but only when the visited member has a * special new name. A special name is a name that might have been produced by * a SpecialNameFactory. * * @see MemberObfuscator * @see SpecialNameFactory * * @author Eric Lafortune */ public class MemberSpecialNameFilter implements MemberVisitor { private final MemberVisitor memberVisitor; /** * Creates a new MemberSpecialNameFilter. * @param memberVisitor the MemberVisitor to which * visits will be delegated. */ public MemberSpecialNameFilter(MemberVisitor memberVisitor) { this.memberVisitor = memberVisitor; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { if (hasSpecialName(programField)) { memberVisitor.visitProgramField(programClass, programField); } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (hasSpecialName(programMethod)) { memberVisitor.visitProgramMethod(programClass, programMethod); } } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { if (hasSpecialName(libraryField)) { memberVisitor.visitLibraryField(libraryClass, libraryField); } } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { if (hasSpecialName(libraryMethod)) { memberVisitor.visitLibraryMethod(libraryClass, libraryMethod); } } // Small utility methods. /** * Returns whether the given class member has a special new name. * @param member the class member. */ private static boolean hasSpecialName(Member member) { return SpecialNameFactory.isSpecialName(MemberObfuscator.newMemberName(member)); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/MultiMappingProcessor.java ================================================ /* * 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.obfuscate; /** * This MappingKeeper delegates all method calls to each MappingProcessor * in a given list. * * @author Eric Lafortune */ public class MultiMappingProcessor implements MappingProcessor { private final MappingProcessor[] mappingProcessors; /** * Creates a new MultiMappingProcessor. * @param mappingProcessors the mapping processors to which method calls * will be delegated. */ public MultiMappingProcessor(MappingProcessor[] mappingProcessors) { this.mappingProcessors = mappingProcessors; } // Implementations for MappingProcessor. public boolean processClassMapping(String className, String newClassName) { boolean result = false; for (int index = 0; index < mappingProcessors.length; index++) { result |= mappingProcessors[index].processClassMapping(className, newClassName); } return result; } public void processFieldMapping(String className, String fieldType, String fieldName, String newClassName, String newFieldName) { for (int index = 0; index < mappingProcessors.length; index++) { mappingProcessors[index].processFieldMapping(className, fieldType, fieldName, newClassName, newFieldName); } } public void processMethodMapping(String className, int firstLineNumber, int lastLineNumber, String methodReturnType, String methodName, String methodArguments, String newClassName, int newFirstLineNumber, int newLastLineNumber, String newMethodName) { for (int index = 0; index < mappingProcessors.length; index++) { mappingProcessors[index].processMethodMapping(className, firstLineNumber, lastLineNumber, methodReturnType, methodName, methodArguments, newClassName, newFirstLineNumber, newLastLineNumber, newMethodName); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/NameFactory.java ================================================ /* * 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.obfuscate; /** * This interfaces provides methods to generate unique sequences of names. * The names must be valid Java identifiers. * * @author Eric Lafortune */ public interface NameFactory { public void reset(); public String nextName(); } ================================================ FILE: base/src/main/java/proguard/obfuscate/NameFactoryResetter.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor resets a given name factory whenever it visits a class * file. * * @author Eric Lafortune */ public class NameFactoryResetter implements ClassVisitor { private final NameFactory nameFactory; /** * Creates a new NameFactoryResetter. * @param nameFactory the name factory to be reset. */ public NameFactoryResetter(NameFactory nameFactory) { this.nameFactory = nameFactory; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { nameFactory.reset(); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/NameMarker.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; /** * This ClassVisitor and MemberVisitor * marks names of the classes and class members it visits. The marked names * will remain unchanged in the obfuscation step. * * @see ClassObfuscator * @see MemberObfuscator * * @author Eric Lafortune */ class NameMarker implements ClassVisitor, MemberVisitor, AttributeVisitor, InnerClassesInfoVisitor, ConstantVisitor { // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { keepClassName(programClass); // Make sure any outer class names are kept as well. programClass.attributesAccept(this); } @Override public void visitLibraryClass(LibraryClass libraryClass) { keepClassName(libraryClass); } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { keepFieldName(programClass, programField); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { keepMethodName(programClass, programMethod); } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { keepFieldName(libraryClass, libraryField); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { keepMethodName(libraryClass, libraryMethod); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { // Make sure the outer class names are kept as well. innerClassesAttribute.innerClassEntriesAccept(clazz, this); } // Implementations for InnerClassesInfoVisitor. public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { // Make sure the outer class name is kept as well. int innerClassIndex = innerClassesInfo.u2innerClassIndex; int outerClassIndex = innerClassesInfo.u2outerClassIndex; if (innerClassIndex != 0 && outerClassIndex != 0 && clazz.getClassName(innerClassIndex).equals(clazz.getName())) { clazz.constantPoolEntryAccept(outerClassIndex, this); } } // Implementations for ConstantVisitor. public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Make sure the outer class name is kept as well. classConstant.referencedClassAccept(this); } // Small utility method. /** * Ensures the name of the given class name will be kept. */ public void keepClassName(Clazz clazz) { ClassObfuscator.setNewClassName(clazz, clazz.getName()); } /** * Ensures the name of the given field name will be kept. */ private void keepFieldName(Clazz clazz, Field field) { MemberObfuscator.setFixedNewMemberName(field, field.getName(clazz)); } /** * Ensures the name of the given method name will be kept. */ private void keepMethodName(Clazz clazz, Method method) { String name = method.getName(clazz); if (!ClassUtil.isInitializer(name)) { MemberObfuscator.setFixedNewMemberName(method, name); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/NameObfuscationReferenceFixer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard.obfuscate; import proguard.AppView; import proguard.Configuration; import proguard.obfuscate.kotlin.KotlinModuleFixer; import proguard.pass.Pass; import proguard.resources.file.visitor.ResourceFileProcessingFlagFilter; import proguard.util.ProcessingFlags; /** * This pass fixes references between Java code and resource files. * * @see Obfuscator * * @author Tim Van Den Broecke */ public class NameObfuscationReferenceFixer implements Pass { private final Configuration configuration; public NameObfuscationReferenceFixer(Configuration configuration) { this.configuration = configuration; } @Override public void execute(AppView appView) { if (configuration.keepKotlinMetadata) { // Fix the Kotlin modules so the filename matches and the class names match. appView.resourceFilePool.resourceFilesAccept( new ResourceFileProcessingFlagFilter(0, ProcessingFlags.DONT_PROCESS_KOTLIN_MODULE, new KotlinModuleFixer())); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/NewMemberNameFilter.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor delegates its visits to another given * MemberVisitor, but only when the visited member has a new name. * Constructors are judged based on the class name. * * @see ClassObfuscator * @see MemberObfuscator * * @author Eric Lafortune */ public class NewMemberNameFilter implements MemberVisitor { private final MemberVisitor memberVisitor; /** * Creates a new MemberNameFilter. * @param memberVisitor the MemberVisitor to which * visits will be delegated. */ public NewMemberNameFilter(MemberVisitor memberVisitor) { this.memberVisitor = memberVisitor; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { if (hasName(programField)) { memberVisitor.visitProgramField(programClass, programField); } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (hasName(programClass, programMethod)) { memberVisitor.visitProgramMethod(programClass, programMethod); } } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { if (hasName(libraryField)) { memberVisitor.visitLibraryField(libraryClass, libraryField); } } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { if (hasName(libraryClass, libraryMethod)) { memberVisitor.visitLibraryMethod(libraryClass, libraryMethod); } } // Small utility methods. /** * Returns whether the given class has a new name. */ private boolean hasName(Clazz clazz) { return ClassObfuscator.newClassName(clazz) != null; } /** * Returns whether the given method has a new name. */ private boolean hasName(Clazz clazz, Method method) { return hasName(method) || (hasName(clazz) && method.getName(clazz).equals(ClassConstants.METHOD_NAME_INIT)); } /** * Returns whether the given class member has a new name. */ private boolean hasName(Member member) { return MemberObfuscator.newMemberName(member) != null; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/NumericNameFactory.java ================================================ /* * 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.obfuscate; /** * This NameFactory generates unique numeric names, starting at * "1". * * @author Eric Lafortune */ public class NumericNameFactory implements NameFactory { private int index; // Implementations for NameFactory. public void reset() { index = 0; } public String nextName() { return Integer.toString(++index); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/ObfuscationPreparation.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.obfuscate; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.Configuration; import proguard.classfile.visitor.ClassCleaner; import proguard.pass.Pass; import proguard.util.PrintWriterUtil; import java.io.*; public class ObfuscationPreparation implements Pass { private static final Logger logger = LogManager.getLogger(ObfuscationPreparation.class); private final Configuration configuration; public ObfuscationPreparation(Configuration configuration) { this.configuration = configuration; } @Override public void execute(AppView appView) throws IOException { // We'll apply a mapping, if requested. if (configuration.applyMapping != null) { logger.info("Applying mapping from [{}]...", PrintWriterUtil.fileName(configuration.applyMapping)); } // Check if we have at least some keep commands. if (configuration.keep == null && configuration.applyMapping == null && configuration.printMapping == null) { throw new IOException("You have to specify '-keep' options for the obfuscation step."); } // Clean up any old processing info. appView.programClassPool.classesAccept(new ClassCleaner()); appView.libraryClassPool.classesAccept(new ClassCleaner()); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/Obfuscator.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.obfuscate; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.Configuration; import proguard.classfile.AccessConstants; import proguard.classfile.VersionConstants; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AllBootstrapMethodInfoVisitor; import proguard.classfile.attribute.visitor.AllInnerClassesInfoVisitor; import proguard.classfile.attribute.visitor.AttributeNameFilter; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.attribute.visitor.MultiAttributeVisitor; import proguard.classfile.attribute.visitor.NonEmptyAttributeFilter; import proguard.classfile.attribute.visitor.RequiredAttributeFilter; import proguard.classfile.constant.Constant; import proguard.classfile.constant.visitor.AllBootstrapMethodArgumentVisitor; import proguard.classfile.constant.visitor.AllConstantVisitor; import proguard.classfile.constant.visitor.ConstantTagFilter; import proguard.classfile.editor.AccessFixer; import proguard.classfile.editor.BridgeMethodFixer; import proguard.classfile.editor.ClassReferenceFixer; import proguard.classfile.editor.ConstantPoolShrinker; import proguard.classfile.editor.InnerClassesAccessFixer; import proguard.classfile.editor.MemberReferenceFixer; import proguard.classfile.kotlin.visitor.AllConstructorVisitor; import proguard.classfile.kotlin.visitor.AllFunctionVisitor; import proguard.classfile.kotlin.visitor.AllPropertyVisitor; import proguard.classfile.kotlin.visitor.AllTypeVisitor; import proguard.classfile.kotlin.visitor.AllValueParameterVisitor; import proguard.classfile.kotlin.visitor.KotlinClassToDefaultImplsClassVisitor; import proguard.classfile.kotlin.visitor.KotlinFunctionToDefaultMethodVisitor; import proguard.classfile.kotlin.visitor.KotlinMetadataToClazzVisitor; import proguard.classfile.kotlin.visitor.MethodToKotlinFunctionVisitor; import proguard.classfile.kotlin.visitor.MultiKotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.filter.KotlinClassKindFilter; import proguard.classfile.kotlin.visitor.filter.KotlinSyntheticClassKindFilter; import proguard.classfile.util.MethodLinker; import proguard.classfile.util.WarningLogger; import proguard.classfile.util.WarningPrinter; import proguard.classfile.visitor.AllMemberVisitor; import proguard.classfile.visitor.AllMethodVisitor; import proguard.classfile.visitor.BottomClassFilter; import proguard.classfile.visitor.ClassAccessFilter; import proguard.classfile.visitor.ClassCounter; import proguard.classfile.visitor.ClassHierarchyTraveler; import proguard.classfile.visitor.ClassProcessingFlagFilter; import proguard.classfile.visitor.ClassVersionFilter; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.DynamicReturnedClassVisitor; import proguard.classfile.visitor.FunctionalInterfaceFilter; import proguard.classfile.visitor.MemberAccessFilter; import proguard.classfile.visitor.MemberCounter; import proguard.classfile.visitor.MemberProcessingFlagFilter; import proguard.classfile.visitor.MethodFilter; import proguard.classfile.visitor.MultiClassVisitor; import proguard.classfile.visitor.ProgramClassFilter; import proguard.classfile.visitor.ProgramMemberFilter; import proguard.classfile.visitor.ReferencedClassVisitor; import proguard.fixer.kotlin.KotlinAnnotationFlagFixer; import proguard.obfuscate.kotlin.KotlinAliasNameObfuscator; import proguard.obfuscate.kotlin.KotlinAliasReferenceFixer; import proguard.obfuscate.kotlin.KotlinCallableReferenceFixer; import proguard.obfuscate.kotlin.KotlinCompanionEqualizer; import proguard.obfuscate.kotlin.KotlinDefaultImplsMethodNameEqualizer; import proguard.obfuscate.kotlin.KotlinDefaultMethodNameEqualizer; import proguard.obfuscate.kotlin.KotlinIntrinsicsReplacementSequences; import proguard.obfuscate.kotlin.KotlinModuleNameObfuscator; import proguard.obfuscate.kotlin.KotlinMultiFileFacadeFixer; import proguard.obfuscate.kotlin.KotlinObjectFixer; import proguard.obfuscate.kotlin.KotlinPropertyNameObfuscator; import proguard.obfuscate.kotlin.KotlinPropertyRenamer; import proguard.obfuscate.kotlin.KotlinSourceDebugExtensionAttributeObfuscator; import proguard.obfuscate.kotlin.KotlinSyntheticClassFixer; import proguard.obfuscate.kotlin.KotlinSyntheticToStringObfuscator; import proguard.obfuscate.kotlin.KotlinUnsupportedExceptionReplacementSequences; import proguard.obfuscate.kotlin.KotlinValueParameterNameShrinker; import proguard.obfuscate.kotlin.KotlinValueParameterUsageMarker; import proguard.obfuscate.util.InstructionSequenceObfuscator; import proguard.pass.Pass; import proguard.resources.file.visitor.ResourceFileProcessingFlagFilter; import proguard.util.PrintWriterUtil; import proguard.util.ProcessingFlags; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * This pass can perform obfuscation of class pools according to a given * specification. * * @author Eric Lafortune */ public class Obfuscator implements Pass { private static final Logger logger = LogManager.getLogger(Obfuscator.class); private final Configuration configuration; public Obfuscator(Configuration configuration) { this.configuration = configuration; } /** * Performs obfuscation of the given program class pool. */ @Override public void execute(AppView appView) throws IOException { logger.info("Obfuscating..."); // We're using the system's default character encoding for writing to // the standard output and error output. PrintWriter out = new PrintWriter(System.out, true); // Link all non-private, non-static methods in all class hierarchies. ClassVisitor memberInfoLinker = new BottomClassFilter(new MethodLinker()); appView.programClassPool.classesAccept(memberInfoLinker); appView.libraryClassPool.classesAccept(memberInfoLinker); // If the class member names have to correspond globally, // additionally link all class members in all program classes. if (configuration.useUniqueClassMemberNames) { appView.programClassPool.classesAccept(new AllMemberVisitor( new MethodLinker())); } // Create a visitor for marking the seeds. NameMarker nameMarker = new NameMarker(); // All library classes and library class members keep their names. appView.libraryClassPool.classesAccept(nameMarker); appView.libraryClassPool.classesAccept(new AllMemberVisitor(nameMarker)); // Mark classes that have the DONT_OBFUSCATE flag set. appView.programClassPool.classesAccept( new MultiClassVisitor( new ClassProcessingFlagFilter(ProcessingFlags.DONT_OBFUSCATE, 0, nameMarker), new AllMemberVisitor( new MemberProcessingFlagFilter(ProcessingFlags.DONT_OBFUSCATE, 0, nameMarker)))); // We also keep the names of the abstract methods of functional // interfaces referenced from bootstrap method arguments (additional // interfaces with LambdaMetafactory.altMetafactory). // The functional method names have to match the names in the // dynamic method invocations with LambdaMetafactory. appView.programClassPool.classesAccept( new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, new AllAttributeVisitor( new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, new AllBootstrapMethodInfoVisitor( new AllBootstrapMethodArgumentVisitor( new ConstantTagFilter(Constant.CLASS, new ReferencedClassVisitor( new FunctionalInterfaceFilter( new ClassHierarchyTraveler(true, false, true, false, new AllMethodVisitor( new MemberAccessFilter(AccessConstants.ABSTRACT, 0, nameMarker)))))))))))); // We also keep the names of the abstract methods of functional // interfaces that are returned by dynamic method invocations. // The functional method names have to match the names in the // dynamic method invocations with LambdaMetafactory. appView.programClassPool.classesAccept( new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, new AllConstantVisitor( new DynamicReturnedClassVisitor( new FunctionalInterfaceFilter( new ClassHierarchyTraveler(true, false, true, false, new AllMethodVisitor( new MemberAccessFilter(AccessConstants.ABSTRACT, 0, nameMarker)))))))); if (configuration.keepKotlinMetadata) { appView.programClassPool.classesAccept( // Keep Kotlin default implementations class where the user had already kept the interface. new ClassProcessingFlagFilter(ProcessingFlags.DONT_OBFUSCATE, 0, new ReferencedKotlinMetadataVisitor(new KotlinClassToDefaultImplsClassVisitor(nameMarker)))); } // Mark attributes that have to be kept. AttributeVisitor attributeUsageMarker = new NonEmptyAttributeFilter( new AttributeUsageMarker()); AttributeVisitor optionalAttributeUsageMarker = configuration.keepAttributes == null ? null : new AttributeNameFilter(configuration.keepAttributes, attributeUsageMarker); appView.programClassPool.classesAccept( new AllAttributeVisitor(true, new RequiredAttributeFilter(attributeUsageMarker, optionalAttributeUsageMarker))); // Keep parameter names and types if specified. if (configuration.keepParameterNames) { appView.programClassPool.classesAccept( // Only visits methods that have a name set in their processing info. // At this step, all methods that have to be kept have been marked // by the NameMarker with their original name, so this will visit // only methods that will not be obfuscated. new AllMethodVisitor( new NewMemberNameFilter( new AllAttributeVisitor(true, new ParameterNameMarker(attributeUsageMarker))))); if (configuration.keepKotlinMetadata) { appView.programClassPool.classesAccept( new MultiClassVisitor( // javac and kotlinc don't create the required attributes on interface methods // so we conservatively mark the parameters as used. new ClassAccessFilter(AccessConstants.INTERFACE, 0, new AllMethodVisitor( new NewMemberNameFilter( new MethodToKotlinFunctionVisitor( new AllValueParameterVisitor( new KotlinValueParameterUsageMarker()))))), // T14916: Annotation classes don't have underlying JVM constructors, // so we conservatively mark the parameters as used, if the class is kept. new ClassAccessFilter(AccessConstants.INTERFACE, 0, new ClassProcessingFlagFilter(ProcessingFlags.DONT_OBFUSCATE, 0, new ReferencedKotlinMetadataVisitor( new KotlinClassKindFilter(metadata -> metadata.flags.isAnnotationClass, new AllConstructorVisitor( new AllValueParameterVisitor( new KotlinValueParameterUsageMarker())))))), // For all other classes, first check if we should keep // the parameter names. new ReferencedKotlinMetadataVisitor( new KotlinValueParameterUsageMarker()))); } } if (configuration.keepKotlinMetadata) { appView.programClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( new KotlinValueParameterNameShrinker())); // Keep SourceDebugExtension annotations on Kotlin synthetic classes but obfuscate them. appView.programClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( new KotlinSyntheticClassKindFilter( KotlinSyntheticClassKindFilter::isLambda, new KotlinMetadataToClazzVisitor( new AllAttributeVisitor( new AttributeNameFilter(Attribute.SOURCE_DEBUG_EXTENSION, new MultiAttributeVisitor(attributeUsageMarker, new KotlinSourceDebugExtensionAttributeObfuscator()))))))); } // Remove the attributes that can be discarded. Note that the attributes // may only be discarded after the seeds have been marked, since the // configuration may rely on annotations. appView.programClassPool.classesAccept(new AttributeShrinker()); if (configuration.keepKotlinMetadata) { appView.programClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( new KotlinAnnotationFlagFixer())); } // Apply the mapping, if one has been specified. The mapping can // override the names of library classes and of library class members. if (configuration.applyMapping != null) { logger.info("Applying mapping from [{}]...", PrintWriterUtil.fileName(configuration.applyMapping)); WarningPrinter warningPrinter = new WarningLogger(logger, configuration.warn); MappingReader reader = new MappingReader(configuration.applyMapping); MappingProcessor keeper = new MultiMappingProcessor(new MappingProcessor[] { new MappingKeeper(appView.programClassPool, warningPrinter), new MappingKeeper(appView.libraryClassPool, null), }); reader.pump(keeper); // Print out a summary of the warnings if necessary. int warningCount = warningPrinter.getWarningCount(); if (warningCount > 0) { logger.warn("Warning: there were {} kept classes and class members that were remapped anyway.", warningCount); logger.warn(" You should adapt your configuration or edit the mapping file."); if (!configuration.ignoreWarnings) { logger.warn(" If you are sure this remapping won't hurt,"); logger.warn(" you could try your luck using the '-ignorewarnings' option."); } logger.warn(" (https://www.guardsquare.com/proguard/manual/troubleshooting#mappingconflict1)"); if (!configuration.ignoreWarnings) { throw new IOException("Please correct the above warnings first."); } } } // Come up with new names for all classes. DictionaryNameFactory classNameFactory = configuration.classObfuscationDictionary != null ? new DictionaryNameFactory(configuration.classObfuscationDictionary, null) : null; DictionaryNameFactory packageNameFactory = configuration.packageObfuscationDictionary != null ? new DictionaryNameFactory(configuration.packageObfuscationDictionary, null) : null; appView.programClassPool.classesAccept( new ClassObfuscator(appView.programClassPool, appView.libraryClassPool, classNameFactory, packageNameFactory, configuration.useMixedCaseClassNames, configuration.keepPackageNames, configuration.flattenPackageHierarchy, configuration.repackageClasses, configuration.allowAccessModification, configuration.keepKotlinMetadata)); if (configuration.keepKotlinMetadata) { // Ensure that the companion instance field is named // the same as the companion class. appView.programClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( new KotlinCompanionEqualizer()) ); } // Come up with new names for all class members. NameFactory nameFactory = new SimpleNameFactory(); if (configuration.obfuscationDictionary != null) { nameFactory = new DictionaryNameFactory(configuration.obfuscationDictionary, nameFactory); } WarningPrinter warningPrinter = new WarningLogger(logger, configuration.warn); // Maintain a map of names to avoid [descriptor - new name - old name]. Map descriptorMap = new HashMap(); // Do the class member names have to be globally unique? if (configuration.useUniqueClassMemberNames) { // Collect all member names in all classes. appView.programClassPool.classesAccept( new AllMemberVisitor( new MemberNameCollector(configuration.overloadAggressively, descriptorMap))); // Assign new names to all members in all classes. appView.programClassPool.classesAccept( new AllMemberVisitor( new MemberObfuscator(configuration.overloadAggressively, nameFactory, descriptorMap))); } else { // Come up with new names for all non-private class members. appView.programClassPool.classesAccept( new MultiClassVisitor( // Collect all private member names in this class and down // the hierarchy. new ClassHierarchyTraveler(true, false, false, true, new AllMemberVisitor( new MemberAccessFilter(AccessConstants.PRIVATE, 0, new MemberNameCollector(configuration.overloadAggressively, descriptorMap)))), // Collect all non-private member names anywhere in the // hierarchy. new ClassHierarchyTraveler(true, true, true, true, new AllMemberVisitor( new MemberAccessFilter(0, AccessConstants.PRIVATE, new MemberNameCollector(configuration.overloadAggressively, descriptorMap)))), // Assign new names to all non-private members in this class. new AllMemberVisitor( new MemberAccessFilter(0, AccessConstants.PRIVATE, new MemberObfuscator(configuration.overloadAggressively, nameFactory, descriptorMap))), // Clear the collected names. new MapCleaner(descriptorMap) )); // Come up with new names for all private class members. appView.programClassPool.classesAccept( new MultiClassVisitor( // Collect all member names in this class. new AllMemberVisitor( new MemberNameCollector(configuration.overloadAggressively, descriptorMap)), // Collect all non-private member names higher up the hierarchy. new ClassHierarchyTraveler(false, true, true, false, new AllMemberVisitor( new MemberAccessFilter(0, AccessConstants.PRIVATE, new MemberNameCollector(configuration.overloadAggressively, descriptorMap)))), // Collect all member names from interfaces of abstract // classes down the hierarchy. // Due to an error in the JLS/JVMS, virtual invocations // may end up at a private method otherwise (Sun/Oracle // bugs #6691741 and #6684387, ProGuard bug #3471941, // and ProGuard test #1180). new ClassHierarchyTraveler(false, false, false, true, new ClassAccessFilter(AccessConstants.ABSTRACT, 0, new ClassHierarchyTraveler(false, false, true, false, new AllMemberVisitor( new MemberNameCollector(configuration.overloadAggressively, descriptorMap))))), // Collect all default method names from interfaces of // any classes down the hierarchy. // This is an extended version of the above problem // (Sun/Oracle bug #802464, ProGuard bug #662, and // ProGuard test #2060). new ClassHierarchyTraveler(false, false, false, true, new ClassHierarchyTraveler(false, false, true, false, new AllMethodVisitor( new MemberAccessFilter(0, AccessConstants.ABSTRACT | AccessConstants.STATIC, new MemberNameCollector(configuration.overloadAggressively, descriptorMap))))), // Assign new names to all private members in this class. new AllMemberVisitor( new MemberAccessFilter(AccessConstants.PRIVATE, 0, new MemberObfuscator(configuration.overloadAggressively, nameFactory, descriptorMap))), // Clear the collected names. new MapCleaner(descriptorMap) )); } // Some class members may have ended up with conflicting names. // Come up with new, globally unique names for them. NameFactory specialNameFactory = new SpecialNameFactory(new SimpleNameFactory()); // Collect a map of special names to avoid // [descriptor - new name - old name]. Map specialDescriptorMap = new HashMap(); appView.programClassPool.classesAccept( new AllMemberVisitor( new MemberSpecialNameFilter( new MemberNameCollector(configuration.overloadAggressively, specialDescriptorMap)))); appView.libraryClassPool.classesAccept( new AllMemberVisitor( new MemberSpecialNameFilter( new MemberNameCollector(configuration.overloadAggressively, specialDescriptorMap)))); // Replace conflicting non-private member names with special names. appView.programClassPool.classesAccept( new MultiClassVisitor( // Collect all private member names in this class and down // the hierarchy. new ClassHierarchyTraveler(true, false, false, true, new AllMemberVisitor( new MemberAccessFilter(AccessConstants.PRIVATE, 0, new MemberNameCollector(configuration.overloadAggressively, descriptorMap)))), // Collect all non-private member names in this class and // higher up the hierarchy. new ClassHierarchyTraveler(true, true, true, false, new AllMemberVisitor( new MemberAccessFilter(0, AccessConstants.PRIVATE, new MemberNameCollector(configuration.overloadAggressively, descriptorMap)))), // Assign new names to all conflicting non-private members // in this class and higher up the hierarchy. new ClassHierarchyTraveler(true, true, true, false, new AllMemberVisitor( new MemberAccessFilter(0, AccessConstants.PRIVATE, new MemberNameConflictFixer(configuration.overloadAggressively, descriptorMap, warningPrinter, new MemberObfuscator(configuration.overloadAggressively, specialNameFactory, specialDescriptorMap))))), // Clear the collected names. new MapCleaner(descriptorMap) )); // Replace conflicting private member names with special names. // This is only possible if those names were kept or mapped. appView.programClassPool.classesAccept( new MultiClassVisitor( // Collect all member names in this class. new AllMemberVisitor( new MemberNameCollector(configuration.overloadAggressively, descriptorMap)), // Collect all non-private member names higher up the hierarchy. new ClassHierarchyTraveler(false, true, true, false, new AllMemberVisitor( new MemberAccessFilter(0, AccessConstants.PRIVATE, new MemberNameCollector(configuration.overloadAggressively, descriptorMap)))), // Assign new names to all conflicting private members in this // class. new AllMemberVisitor( new MemberAccessFilter(AccessConstants.PRIVATE, 0, new MemberNameConflictFixer(configuration.overloadAggressively, descriptorMap, warningPrinter, new MemberObfuscator(configuration.overloadAggressively, specialNameFactory, specialDescriptorMap)))), // Clear the collected names. new MapCleaner(descriptorMap) )); // Print out any warnings about member name conflicts. int warningCount = warningPrinter.getWarningCount(); if (warningCount > 0) { logger.warn("Warning: there were {} conflicting class member name mappings.", warningCount); logger.warn(" Your configuration may be inconsistent."); if (!configuration.ignoreWarnings) { logger.warn(" If you are sure the conflicts are harmless,"); logger.warn(" you could try your luck using the '-ignorewarnings' option."); } logger.warn(" (https://www.guardsquare.com/proguard/manual/troubleshooting#mappingconflict2)"); if (!configuration.ignoreWarnings) { throw new IOException("Please correct the above warnings first."); } } // Obfuscate the Intrinsics.check* method calls. appView.programClassPool.classesAccept( new InstructionSequenceObfuscator( new KotlinIntrinsicsReplacementSequences(appView.programClassPool, appView.libraryClassPool)) ); if (configuration.keepKotlinMetadata) { appView.programClassPool.classesAccept( new MultiClassVisitor( new ReferencedKotlinMetadataVisitor( new MultiKotlinMetadataVisitor( // Come up with new names for Kotlin Properties. new KotlinPropertyNameObfuscator(nameFactory), // Obfuscate alias names. new KotlinAliasNameObfuscator(nameFactory), // Equalise/fix $DefaultImpls and $WhenMappings classes. new KotlinSyntheticClassFixer(), // Ensure object classes have the INSTANCE field. new KotlinObjectFixer(), new AllFunctionVisitor( // Ensure that all default interface implementations of methods have the same names. new KotlinDefaultImplsMethodNameEqualizer(), // Ensure all $default methods match their counterpart but with a $default suffix. new KotlinDefaultMethodNameEqualizer(), // Obfuscate the throw new UnsupportedOperationExceptions in $default methods // because they contain the original function name in the string. new KotlinFunctionToDefaultMethodVisitor( new InstructionSequenceObfuscator( new KotlinUnsupportedExceptionReplacementSequences(appView.programClassPool, appView.libraryClassPool))) ), // Obfuscate toString & toString-impl methods in data classes and inline/value classes. new KotlinClassKindFilter( kc -> (kc.flags.isValue || kc.flags.isData), new KotlinSyntheticToStringObfuscator())) ) )); appView.resourceFilePool.resourceFilesAccept( new ResourceFileProcessingFlagFilter(0, ProcessingFlags.DONT_OBFUSCATE, new KotlinModuleNameObfuscator(nameFactory))); } // Print out the mapping, if requested. if (configuration.printMapping != null) { logger.info("Printing mapping to [{}]...", PrintWriterUtil.fileName(configuration.printMapping)); PrintWriter mappingWriter = PrintWriterUtil.createPrintWriter(configuration.printMapping, out); try { // Print out items that will be renamed. appView.programClassPool.classesAcceptAlphabetically( new MappingPrinter(mappingWriter)); } finally { PrintWriterUtil.closePrintWriter(configuration.printMapping, mappingWriter); } } if (configuration.addConfigurationDebugging) { appView.programClassPool.classesAccept(new RenamedFlagSetter()); } // Collect some statistics about the number of obfuscated // classes and members. ClassCounter obfuscatedClassCounter = new ClassCounter(); MemberCounter obfuscatedFieldCounter = new MemberCounter(); MemberCounter obfuscatedMethodCounter = new MemberCounter(); ClassVisitor classRenamer = new proguard.obfuscate.ClassRenamer( new ProgramClassFilter( obfuscatedClassCounter), new ProgramMemberFilter( new MethodFilter( obfuscatedMethodCounter, obfuscatedFieldCounter)) ); if (configuration.keepKotlinMetadata) { // Ensure multi-file parts and facades are in the same package. appView.programClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( new KotlinMultiFileFacadeFixer())); } // Actually apply the new names. appView.programClassPool.classesAccept(classRenamer); appView.libraryClassPool.classesAccept(classRenamer); if (configuration.keepKotlinMetadata) { // Apply new names to Kotlin properties. appView.programClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( new AllPropertyVisitor( new KotlinPropertyRenamer()))); } // Update all references to these new names. appView.programClassPool.classesAccept(new ClassReferenceFixer(false)); appView.libraryClassPool.classesAccept(new ClassReferenceFixer(false)); appView.programClassPool.classesAccept(new MemberReferenceFixer(configuration.android)); if (configuration.keepKotlinMetadata) { appView.programClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( new MultiKotlinMetadataVisitor( new AllTypeVisitor( // Fix all the alias references. new KotlinAliasReferenceFixer()), // Fix all the CallableReference interface methods to match the new names. new KotlinCallableReferenceFixer(appView.programClassPool, appView.libraryClassPool)))); } // Make package visible elements public or protected, if obfuscated // classes are being repackaged aggressively. if (configuration.repackageClasses != null && configuration.allowAccessModification) { appView.programClassPool.classesAccept( new AccessFixer()); // Fix the access flags of the inner classes information. // Don't change the access flags of inner classes that // have not been renamed (Guice). [DGD-63] appView.programClassPool.classesAccept( new OriginalClassNameFilter(null, new AllAttributeVisitor( new AllInnerClassesInfoVisitor( new InnerClassesAccessFixer())))); } // Fix the bridge method flags. appView.programClassPool.classesAccept( new AllMethodVisitor( new BridgeMethodFixer())); // Rename the source file attributes, if requested. if (configuration.newSourceFileAttribute != null) { appView.programClassPool.classesAccept(new SourceFileRenamer(configuration.newSourceFileAttribute)); } // Remove unused constants. appView.programClassPool.classesAccept( new ConstantPoolShrinker()); logger.info(" Number of obfuscated classes: {}", obfuscatedClassCounter.getCount()); logger.info(" Number of obfuscated fields: {}", obfuscatedFieldCounter.getCount()); logger.info(" Number of obfuscated methods: {}", obfuscatedMethodCounter.getCount()); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/OriginalClassNameFilter.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates its visits to one of two other ClassVisitor's, * depending on whether the visited class still has its original name or not. * * @see ClassObfuscator * * @author Johan Leys */ public class OriginalClassNameFilter implements ClassVisitor { private final ClassVisitor acceptedClassVisitor; private final ClassVisitor rejectedClassVisitor; /** * Creates a new OriginalClassNameFilter. * * @param acceptedClassVisitor the class visitor to which accepted classes * (classes that still have their original * name) will be delegated. * @param rejectedClassVisitor the class visitor to which rejected classes * (classes that have a changed name) will be * delegated. */ public OriginalClassNameFilter(ClassVisitor acceptedClassVisitor, ClassVisitor rejectedClassVisitor) { this.acceptedClassVisitor = acceptedClassVisitor; this.rejectedClassVisitor = rejectedClassVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { ClassVisitor delegateVisitor = selectVisitor(clazz); if (delegateVisitor != null) { clazz.accept(delegateVisitor); } } // Small utility methods. private ClassVisitor selectVisitor(Clazz clazz) { return ClassObfuscator.hasOriginalClassName(clazz) ? acceptedClassVisitor : rejectedClassVisitor; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/ParameterNameMarker.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; /** * This AttributeVisitor trims and marks all local variable (type) table * attributes that it visits. It keeps parameter names and types and removes * the ordinary local variable names and types. * * @author Eric Lafortune */ public class ParameterNameMarker implements AttributeVisitor { private final AttributeVisitor attributeUsageMarker; /** * Constructs a new ParameterNameMarker. * @param attributeUsageMarker the marker that will be used to mark * attributes containing local variable info. */ public ParameterNameMarker(AttributeVisitor attributeUsageMarker) { this.attributeUsageMarker = attributeUsageMarker; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { if (!AttributeUsageMarker.isUsed(localVariableTableAttribute) && hasParameters(clazz, method)) { // Shift the entries that start at offset 0 to the front. int newIndex = 0; for (int index = 0; index < localVariableTableAttribute.u2localVariableTableLength; index++) { LocalVariableInfo localVariableInfo = localVariableTableAttribute.localVariableTable[index]; if (localVariableInfo.u2startPC == 0) { localVariableTableAttribute.localVariableTable[newIndex++] = localVariableInfo; } } // Trim the table. localVariableTableAttribute.u2localVariableTableLength = newIndex; // Mark the table if there are any entries. if (newIndex > 0) { attributeUsageMarker.visitLocalVariableTableAttribute(clazz, method, codeAttribute, localVariableTableAttribute); } } } public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { if (!AttributeUsageMarker.isUsed(localVariableTypeTableAttribute) && hasParameters(clazz, method)) { // Shift the entries that start at offset 0 to the front. int newIndex = 0; for (int index = 0; index < localVariableTypeTableAttribute.u2localVariableTypeTableLength; index++) { LocalVariableTypeInfo localVariableTypeInfo = localVariableTypeTableAttribute.localVariableTypeTable[index]; if (localVariableTypeInfo.u2startPC == 0) { localVariableTypeTableAttribute.localVariableTypeTable[newIndex++] = localVariableTypeInfo; } } // Trim the table. localVariableTypeTableAttribute.u2localVariableTypeTableLength = newIndex; // Mark the table if there are any entries. if (newIndex > 0) { attributeUsageMarker.visitLocalVariableTypeTableAttribute(clazz, method, codeAttribute, localVariableTypeTableAttribute); } } } // Small utility methods. private boolean hasParameters(Clazz clazz, Method method) { return method.getDescriptor(clazz).charAt(1) != TypeConstants.METHOD_ARGUMENTS_CLOSE; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/PrefixingNameFactory.java ================================================ /* * 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.obfuscate; /** * NameFactory that prepends the names of the wrapped NameFactory with * a fixed prefix. * * @author Johan Leys */ public class PrefixingNameFactory implements NameFactory { private final NameFactory delegateNameFactory; private final String prefix; /** * Creates a new PrefixingNameFactory. * @param delegateNameFactory the wrapped NameFactory. * @param prefix the prefix to add to all generated names. */ public PrefixingNameFactory(NameFactory delegateNameFactory, String prefix) { this.delegateNameFactory = delegateNameFactory; this.prefix = prefix; } // Implementations for NameFactory. public String nextName() { return prefix + delegateNameFactory.nextName(); } public void reset() { delegateNameFactory.reset(); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/RenamedFlagSetter.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.visitor.*; /** * This ClassVisitor sets the ACC_RENAMED flag for classes or class members * that have been renamed. * * @author Johan Leys */ public class RenamedFlagSetter implements ClassVisitor, // Implementation interfaces. MemberVisitor, AttributeVisitor { // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { String oldName = programClass.getName(); String newName = ClassObfuscator.newClassName(programClass); if (newName != null && !oldName.equals(newName)) { programClass.u2accessFlags |= AccessConstants.RENAMED; } // Print out the class members. programClass.fieldsAccept(this); programClass.methodsAccept(this); } // Implementations for MemberVisitor. public void visitProgramMember(ProgramClass programClass, ProgramMember programMember) { String oldName = programMember.getName(programClass); String newName = MemberObfuscator.newMemberName(programMember); if (newName != null && !newName.equals(oldName)) { programMember.u2accessFlags |= AccessConstants.RENAMED; } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/ResourceFileNameAdapter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.obfuscate; import proguard.AppView; import proguard.Configuration; import proguard.pass.Pass; import proguard.util.FileNameParser; import proguard.util.ListParser; /** * This pass adapts resource file names that correspond to class names, if necessary. * * @author Tim Van Den Broecke */ public class ResourceFileNameAdapter implements Pass { private final Configuration configuration; public ResourceFileNameAdapter(Configuration configuration) { this.configuration = configuration; } @Override public void execute(AppView appView) { appView.resourceFilePool.resourceFilesAccept( new ListParser(new FileNameParser()).parse(configuration.adaptResourceFileNames), new ResourceFileNameObfuscator(new ClassNameAdapterFunction(appView.programClassPool), true)); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/ResourceFileNameObfuscator.java ================================================ /* * 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.obfuscate; import proguard.resources.file.ResourceFile; import proguard.resources.file.visitor.*; import proguard.util.*; /** * This ResourceFileVisitor obfuscates the names of all visited resource files, * using a given StringFunction to map given names on new, obfuscated names. * * @author Johan Leys */ public class ResourceFileNameObfuscator implements ResourceFileVisitor { private final StringFunction nameObfuscationFunction; private final boolean overrideAlreadyObfuscatedNames; private final ResourceFileVisitor extraVisitor; public ResourceFileNameObfuscator(StringFunction nameObfuscationFunction, boolean overrideAlreadyObfuscatedNames) { this(nameObfuscationFunction, overrideAlreadyObfuscatedNames, null); } public ResourceFileNameObfuscator(StringFunction nameObfuscationFunction, boolean overrideAlreadyObfuscatedNames, ResourceFileVisitor extraVisitor) { this.nameObfuscationFunction = nameObfuscationFunction; this.overrideAlreadyObfuscatedNames = overrideAlreadyObfuscatedNames; this.extraVisitor = extraVisitor; } // Implementations for ResourceFileVisitor. @Override public void visitAnyResourceFile(ResourceFile resourceFile) { if (overrideAlreadyObfuscatedNames || !isObfuscated(resourceFile)) { String obfuscatedFileName = nameObfuscationFunction.transform(resourceFile.fileName); if (obfuscatedFileName != null) { setNewResourceFileName(resourceFile, obfuscatedFileName); } if (extraVisitor != null) { resourceFile.accept(extraVisitor); } } } /** * Assigns a new name to the given resource file. * @param resourceFile the given resource file. * @param newFileName the new name. */ private static void setNewResourceFileName(ResourceFile resourceFile, String newFileName) { // Store the original filename as processing info. resourceFile.setProcessingInfo(resourceFile.fileName); resourceFile.fileName = newFileName; } /** * Returns whether the given resource file has been obfuscated by an instance of this class. * @param resourceFile the given resource file. * @return true if the given resource file has been obfuscated by an instance of this class, false otherwise. */ public static boolean isObfuscated(ResourceFile resourceFile) { String originalResourceFileName = getOriginalResourceFileName(resourceFile); return !resourceFile.getFileName().equals(originalResourceFileName); } /** * Retrieves the original name of the given resource file. * @param resourceFile the given resource file. * @return the resource file's original name. */ public static String getOriginalResourceFileName(ResourceFile resourceFile) { Object processingInfo = resourceFile.getProcessingInfo(); return processingInfo instanceof String ? (String)processingInfo : resourceFile.fileName; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/ResourceJavaReferenceFixer.java ================================================ /* * 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.obfuscate; import proguard.classfile.Clazz; import proguard.classfile.util.ClassUtil; import proguard.resources.file.*; import proguard.resources.file.visitor.ResourceFileVisitor; import proguard.resources.kotlinmodule.KotlinModule; import java.util.Set; /** * This {@link ResourceFileVisitor} fixes the class names of referenced Java * classes whose name have changed. * * @author Lars Vandenbergh */ public class ResourceJavaReferenceFixer implements ResourceFileVisitor { // Implementations for ResourceFileVisitor. @Override public void visitResourceFile(ResourceFile resourceFile) { Set references = resourceFile.references; if (references != null) { for (ResourceJavaReference reference : references) { Clazz referencedClass = reference.referencedClass; if (referencedClass != null) { reference.externalClassName = ClassUtil.externalClassName(referencedClass.getName()); } } } } @Override public void visitKotlinModule(KotlinModule kotlinModule) { } } ================================================ FILE: base/src/main/java/proguard/obfuscate/SimpleNameFactory.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.obfuscate; import java.util.Arrays; /** * This NameFactory generates unique short names, using mixed-case * characters or lower-case characters only. * * @author Eric Lafortune */ public class SimpleNameFactory implements NameFactory { private static final int CHARACTER_COUNT = 26; /** + * Array of windows reserved names. + * This array does not include COM{digit} or LPT{digit} as {@link SimpleNameFactory} does not generate digits. + * This array must be sorted in ascending order as we're using {@link Arrays#binarySearch(Object[], Object)} on it. + */ private static final String[] reservedNames = new String[] {"AUX", "CON", "NUL", "PRN"}; private final boolean generateMixedCaseNames; private int index = 0; /** * Creates a new SimpleNameFactory that generates mixed-case names. */ public SimpleNameFactory() { this(true); } /** * Creates a new SimpleNameFactory. * @param generateMixedCaseNames a flag to indicate whether the generated * names will be mixed-case, or lower-case only. */ public SimpleNameFactory(boolean generateMixedCaseNames) { this.generateMixedCaseNames = generateMixedCaseNames; } // Implementations for NameFactory. public void reset() { index = 0; } public String nextName() { return name(index++); } /** * Returns the name at the given index. */ private String name(int index) { // Create a new name for this index return newName(index); } /** * Creates and returns the name at the given index. */ private String newName(int index) { // If we're allowed to generate mixed-case names, we can use twice as // many characters. int totalCharacterCount = generateMixedCaseNames ? 2 * CHARACTER_COUNT : CHARACTER_COUNT; int baseIndex = index / totalCharacterCount; int offset = index % totalCharacterCount; char newChar = charAt(offset); String newName = baseIndex == 0 ? new String(new char[] { newChar }) : (name(baseIndex-1) + newChar); if (Arrays.binarySearch(reservedNames, newName.toUpperCase()) >= 0) { newName += newChar; } return newName; } /** * Returns the character with the given index, between 0 and the number of * acceptable characters. */ private char charAt(int index) { return (char)((index < CHARACTER_COUNT ? 'a' - 0 : 'A' - CHARACTER_COUNT) + index); } public static void main(String[] args) { System.out.println("Some mixed-case names:"); printNameSamples(new SimpleNameFactory(true), 60); System.out.println("Some lower-case names:"); printNameSamples(new SimpleNameFactory(false), 60); System.out.println("Some more mixed-case names:"); printNameSamples(new SimpleNameFactory(true), 80); System.out.println("Some more lower-case names:"); printNameSamples(new SimpleNameFactory(false), 80); } private static void printNameSamples(SimpleNameFactory factory, int count) { for (int counter = 0; counter < count; counter++) { System.out.println(" ["+factory.nextName()+"]"); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/SourceFileRenamer.java ================================================ /* * 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.obfuscate; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor changes the name stored in the source file attributes * and source dir attributes of the classes that it visits, if the * attributes are present. * * @author Eric Lafortune */ public class SourceFileRenamer implements ClassVisitor, AttributeVisitor { private final String newSourceFileAttribute; /** * Creates a new SourceFileRenamer. * @param newSourceFileAttribute the new string to be put in the source file * attributes. */ public SourceFileRenamer(String newSourceFileAttribute) { this.newSourceFileAttribute = newSourceFileAttribute; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Only visit the class attributes. programClass.attributesAccept(this); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute) { // Fix the source file attribute. sourceFileAttribute.u2sourceFileIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSourceFileAttribute); } public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute) { // Fix the source file attribute. sourceDirAttribute.u2sourceDirIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSourceFileAttribute); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/SpecialNameFactory.java ================================================ /* * 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.obfuscate; /** * This NameFactory generates names that are special, by appending * a suffix. * * @author Eric Lafortune */ public class SpecialNameFactory implements NameFactory { private static final char SPECIAL_SUFFIX = '_'; private final NameFactory nameFactory; /** * Creates a new SpecialNameFactory. * @param nameFactory the name factory from which original names will be * retrieved. */ public SpecialNameFactory(NameFactory nameFactory) { this.nameFactory = nameFactory; } // Implementations for NameFactory. public void reset() { nameFactory.reset(); } public String nextName() { return nameFactory.nextName() + SPECIAL_SUFFIX; } // Small utility methods. /** * Returns whether the given name is special. */ static boolean isSpecialName(String name) { return name != null && name.charAt(name.length()-1) == SPECIAL_SUFFIX; } public static void main(String[] args) { SpecialNameFactory factory = new SpecialNameFactory(new SimpleNameFactory()); for (int counter = 0; counter < 50; counter++) { System.out.println("["+factory.nextName()+"]"); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/UniqueMemberNameFactory.java ================================================ /* * 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.obfuscate; import proguard.classfile.Clazz; /** * NameFactory which only generates names that don't exist yet as members * on the class for which it is created. * * @author Johan Leys */ public class UniqueMemberNameFactory implements NameFactory { private static final String INJECTED_MEMBER_PREFIX = "$$"; private final NameFactory delegateNameFactory; private final Clazz clazz; /** * Utility for creating a new NameFactory that can generate names for injected * members: the generated names are unique within the given class, and don't * clash with non-injected members of its super classes. * * @param clazz the class for which to generate a NameFactory. * @return the new NameFactory instance. */ public static UniqueMemberNameFactory newInjectedMemberNameFactory(Clazz clazz) { return new UniqueMemberNameFactory( new PrefixingNameFactory( new SimpleNameFactory(), INJECTED_MEMBER_PREFIX), clazz); } /** * Creates a new UniqueMemberNameFactory. * @param delegateNameFactory the delegate NameFactory, used for generating * new candidate names. * @param clazz the class in which to check for existing * member names. */ public UniqueMemberNameFactory(NameFactory delegateNameFactory, Clazz clazz) { this.delegateNameFactory = delegateNameFactory; this.clazz = clazz; } // Implementations for NameFactory. public String nextName() { String name; // Check if the name doesn't exist yet. We don't have additional // descriptor information, so we can only search on the name. do { name = delegateNameFactory.nextName(); } while (clazz.findField(name, null) != null || clazz.findMethod(name, null) != null); return name; } public void reset() { delegateNameFactory.reset(); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinAliasNameObfuscator.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.*; import proguard.obfuscate.NameFactory; import proguard.util.ProcessingFlags; public class KotlinAliasNameObfuscator implements KotlinMetadataVisitor, // Implementation interfaces. KotlinTypeAliasVisitor, KotlinTypeVisitor { private final NameFactory nameFactory; public KotlinAliasNameObfuscator(NameFactory nameFactory) { this.nameFactory = nameFactory; } // Implementations for KotlinMetadataVisitor. @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) { this.nameFactory.reset(); kotlinDeclarationContainerMetadata.typeAliasesAccept(clazz, this); } @Override public void visitAnyType(Clazz clazz, KotlinTypeMetadata kotlinTypeMetadata) {} @Override public void visitTypeAlias(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinTypeAliasMetadata kotlinTypeAliasMetadata) { kotlinTypeAliasMetadata.expandedTypeAccept(clazz, kotlinDeclarationContainerMetadata, this); } @Override public void visitAliasExpandedType(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinTypeAliasMetadata kotlinTypeAliasMetadata, KotlinTypeMetadata kotlinTypeMetadata) { // If the expanded type class is kept, assume it's a public API so keep the alias also. if ((kotlinTypeMetadata.referencedClass.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) == 0) { kotlinTypeAliasMetadata.name = nameFactory.nextName(); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinAliasReferenceFixer.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.KotlinTypeVisitor; import proguard.classfile.util.ClassUtil; public class KotlinAliasReferenceFixer implements KotlinTypeVisitor { // Implementations for KotlinTypeVisitor. @Override public void visitAnyType(Clazz clazz, KotlinTypeMetadata kotlinTypeMetadata) { if (kotlinTypeMetadata.aliasName != null) { String newName; if (kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer.k == KotlinConstants.METADATA_KIND_CLASS) { // Type alias declared within a class. // Inner classes in Kotlin metadata have a '.' separator instead of the standard '$'. newName = ((KotlinClassKindMetadata)kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer).className + "." + kotlinTypeMetadata.referencedTypeAlias.name; } else { // Top-level alias declaration. // Package is that of the file facade (which is a declaration container). newName = ClassUtil.internalPackagePrefix(kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer.ownerClassName) + kotlinTypeMetadata.referencedTypeAlias.name; } kotlinTypeMetadata.aliasName = newName; } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinCallableReferenceFixer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.obfuscate.kotlin; import proguard.classfile.*; import proguard.classfile.constant.Constant; import proguard.classfile.editor.InstructionSequenceBuilder; import proguard.classfile.instruction.Instruction; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.reflect.util.KotlinCallableReferenceInitializer.OptimizedCallableReferenceFilter; import proguard.classfile.kotlin.reflect.visitor.CallableReferenceInfoToOwnerVisitor; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.filter.KotlinDeclarationContainerFilter; import proguard.classfile.util.InstructionSequenceMatcher; import proguard.classfile.visitor.*; import proguard.obfuscate.util.*; import proguard.resources.kotlinmodule.visitor.KotlinMetadataToModuleVisitor; import static proguard.classfile.ClassConstants.METHOD_NAME_INIT; import static proguard.classfile.kotlin.KotlinConstants.*; import static proguard.classfile.util.InstructionSequenceMatcher.*; /** * This class fixes the CallableReference implementations of function and property references. * * @author James Hamilton */ public class KotlinCallableReferenceFixer implements KotlinMetadataVisitor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; public KotlinCallableReferenceFixer(ClassPool programClassPool, ClassPool libraryClassPool) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; } // Implementations for KotlinMetadataVisitor. @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassKindMetadata syntheticClass) { if (syntheticClass.callableReferenceInfo != null) { clazz.accept( new OptimizedCallableReferenceFilter( new AllMethodVisitor( new MemberNameFilter(METHOD_NAME_INIT, new InstructionSequenceObfuscator( new NameAndSignatureReplacementSequences( syntheticClass.callableReferenceInfo.getName(), syntheticClass.callableReferenceInfo.getSignature(), programClassPool, libraryClassPool))) ), new MultiClassVisitor( new NamedMethodVisitor( REFLECTION.GETNAME_METHOD_NAME, REFLECTION.GETNAME_METHOD_DESC, // getName() returns the Kotlin name of the callable, the one which was declared in the source code (@JvmName doesn't change it). new InstructionSequenceObfuscator( new NameOrSignatureReplacementSequences( syntheticClass.callableReferenceInfo.getName(), programClassPool, libraryClassPool))), new NamedMethodVisitor( REFLECTION.GETSIGNATURE_METHOD_NAME, REFLECTION.GETSIGNATURE_METHOD_DESC, //getSignature() returns the signature. new InstructionSequenceObfuscator( new NameOrSignatureReplacementSequences( syntheticClass.callableReferenceInfo.getSignature(), programClassPool, libraryClassPool))) ) ) ); if (clazz.findMethod(REFLECTION.GETOWNER_METHOD_NAME, REFLECTION.GETOWNER_METHOD_DESC) != null) { // getOwner() returns the Kotlin class or package (for file facades and multi-file class parts) // where the callable should be located, usually specified on the LHS of the '::' operator but it could also be a superclass. // We update getOwner() only for file facades and multi-file class parts because creating a Kotlin package // requires the module name. syntheticClass.callableReferenceInfoAccept( new CallableReferenceInfoToOwnerVisitor( new KotlinDeclarationContainerFilter( declarationContainer -> declarationContainer.k == METADATA_KIND_FILE_FACADE || declarationContainer.k == METADATA_KIND_MULTI_FILE_CLASS_PART, new KotlinMetadataToModuleVisitor( kotlinModule -> clazz.accept( new NamedMethodVisitor( REFLECTION.GETOWNER_METHOD_NAME, REFLECTION.GETOWNER_METHOD_DESC, new InstructionSequenceObfuscator( new OwnerReplacementSequences( kotlinModule.name, programClassPool, libraryClassPool)))))))); } } } public static final class NameOrSignatureReplacementSequences implements ReplacementSequences { private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; NameOrSignatureReplacementSequences(String name, ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); SEQUENCES = new Instruction[][][] { { ____ .ldc_(InstructionSequenceMatcher.X) .areturn().__(), ____ .ldc(name) .areturn().__() }, }; CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } public static final class NameAndSignatureReplacementSequences implements ReplacementSequences { private static final int OWNER_INDEX = A; private static final int NAME_INDEX = B; private static final int SIGNATURE_INDEX = C; private static final int FLAGS_INDEX = D; private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; NameAndSignatureReplacementSequences(String name, String signature, ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); SEQUENCES = new Instruction[][][] { { ____.ldc_(OWNER_INDEX) .ldc_(NAME_INDEX) .ldc_(SIGNATURE_INDEX) .ldc_(FLAGS_INDEX) .invokespecial(X).__(), ____.ldc_(OWNER_INDEX) .ldc(name) .ldc(signature) .ldc_(FLAGS_INDEX) .invokespecial(X).__(), }, { ____.ldc_(OWNER_INDEX) .ldc_(NAME_INDEX) .ldc_(SIGNATURE_INDEX) .iconst(I) .invokespecial(X).__(), ____.ldc_(OWNER_INDEX) .ldc(name) .ldc(signature) .iconst(I) .invokespecial(X).__(), } }; CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } public static final class OwnerReplacementSequences implements ReplacementSequences { private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; OwnerReplacementSequences(String name, ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); SEQUENCES = new Instruction[][][] { { ____ .ldc_(InstructionSequenceMatcher.X) .ldc_(InstructionSequenceMatcher.Y) .invokestatic(REFLECTION.CLASS_NAME, REFLECTION.GETORCREATEKOTLINPACKAGE_METHOD_NAME, REFLECTION.GETORCREATEKOTLINPACKAGE_METHOD_DESC).__(), ____ .ldc_(InstructionSequenceMatcher.X) .ldc(name) .invokestatic(REFLECTION.CLASS_NAME, REFLECTION.GETORCREATEKOTLINPACKAGE_METHOD_NAME, REFLECTION.GETORCREATEKOTLINPACKAGE_METHOD_DESC).__(), }, }; CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinCompanionEqualizer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.kotlin.KotlinClassKindMetadata; import proguard.classfile.kotlin.KotlinMetadata; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; import static proguard.classfile.util.ClassUtil.internalSimpleClassName; import static proguard.obfuscate.ClassObfuscator.newClassName; import static proguard.obfuscate.MemberObfuscator.setFixedNewMemberName; public class KotlinCompanionEqualizer implements KotlinMetadataVisitor { @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { if (kotlinClassKindMetadata.companionObjectName != null) { String newCompanionClassName = newClassName(kotlinClassKindMetadata.referencedCompanionClass); if (newCompanionClassName == null) return; // The name should be an inner class, but if for some reason it isn't // then don't try to rename it as it could lead to problems. // The Kotlin asserter will check the field name, so will throw the metadata away if // it wasn't named correctly. if (newCompanionClassName.contains("$")) { // Set a fixed member name to make sure it gets priority when resolving naming conflicts and collecting // already used member names. setFixedNewMemberName(kotlinClassKindMetadata.referencedCompanionField, internalSimpleClassName(newCompanionClassName)); } } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinDefaultImplsMethodNameEqualizer.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor; import static proguard.obfuscate.MemberObfuscator.*; public class KotlinDefaultImplsMethodNameEqualizer implements KotlinFunctionVisitor { @Override public void visitAnyFunction(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { // Ensure that the names of default implementation methods match. if (kotlinFunctionMetadata.referencedDefaultImplementationMethod != null) { setNewMemberName( kotlinFunctionMetadata.referencedDefaultImplementationMethod, newMemberName(kotlinFunctionMetadata.referencedMethod) ); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinDefaultMethodNameEqualizer.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.*; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor; import static proguard.classfile.kotlin.KotlinConstants.*; import static proguard.obfuscate.MemberObfuscator.*; public class KotlinDefaultMethodNameEqualizer implements KotlinFunctionVisitor { @Override public void visitAnyFunction(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { // Default parameter methods should still have the $default suffix. if (kotlinFunctionMetadata.referencedDefaultMethod != null) { setNewMemberName( kotlinFunctionMetadata.referencedDefaultMethod, newMemberName(kotlinFunctionMetadata.referencedMethod) + DEFAULT_METHOD_SUFFIX ); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinIntrinsicsReplacementSequences.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.*; import proguard.classfile.constant.Constant; import proguard.classfile.editor.InstructionSequenceBuilder; import proguard.classfile.instruction.Instruction; import proguard.classfile.util.InstructionSequenceMatcher; import proguard.obfuscate.util.ReplacementSequences; import static proguard.classfile.kotlin.KotlinConstants.*; public final class KotlinIntrinsicsReplacementSequences implements ReplacementSequences { private static final String REPLACEMENT_STRING = ""; private static final int CONSTANT_INDEX_1 = InstructionSequenceMatcher.X; private static final int CONSTANT_INDEX_2 = InstructionSequenceMatcher.Y; private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; public KotlinIntrinsicsReplacementSequences(ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); SEQUENCES = new Instruction[][][] { { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V").__(), ____ .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkNotNull", "(Ljava/lang/Object;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkExpressionValueIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V").__(), ____ .ldc(REPLACEMENT_STRING) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkExpressionValueIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkNotNullExpressionValue", "(Ljava/lang/Object;Ljava/lang/String;)V").__(), ____ .ldc(REPLACEMENT_STRING) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkNotNullExpressionValue", "(Ljava/lang/Object;Ljava/lang/String;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkParameterIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V").__(), ____ .ldc(REPLACEMENT_STRING) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkParameterIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkNotNullParameter", "(Ljava/lang/Object;Ljava/lang/String;)V").__(), ____ .ldc(REPLACEMENT_STRING) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkNotNullParameter", "(Ljava/lang/Object;Ljava/lang/String;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkReturnedValueIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V").__(), ____ .ldc(REPLACEMENT_STRING) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkReturnedValueIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkFieldIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V").__(), ____ .ldc(REPLACEMENT_STRING) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkFieldIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .ldc_(CONSTANT_INDEX_2) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkReturnedValueIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V").__(), ____ .ldc(REPLACEMENT_STRING) .ldc(REPLACEMENT_STRING) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkReturnedValueIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .ldc_(CONSTANT_INDEX_2) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkFieldIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V").__(), ____ .ldc(REPLACEMENT_STRING) .ldc(REPLACEMENT_STRING) .invokestatic(KOTLIN_INTRINSICS_CLASS, "checkFieldIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwUninitializedProperty", "(Ljava/lang/String;)V").__(), ____ .ldc(REPLACEMENT_STRING) .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwUninitializedProperty", "(Ljava/lang/String;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V").__(), ____ .ldc(REPLACEMENT_STRING) .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwNpe", "(Ljava/lang/String;)V").__(), ____ .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwNpe", "()V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwJavaNpe", "(Ljava/lang/String;)V").__(), ____ .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwJavaNpe", "()V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwAssert", "(Ljava/lang/String;)V").__(), ____ .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwAssert", "()V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwIllegalArgument", "(Ljava/lang/String;)V").__(), ____ .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwIllegalArgument", "()V").__() }, { ____ .ldc_(CONSTANT_INDEX_1) .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwIllegalState", "(Ljava/lang/String;)V").__(), ____ .invokestatic(KOTLIN_INTRINSICS_CLASS, "throwIllegalState", "()V").__() }, }; CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinModuleFixer.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.kotlin.*; import proguard.classfile.util.ClassUtil; import proguard.resources.file.ResourceFile; import proguard.resources.file.visitor.*; import proguard.resources.kotlinmodule.*; import proguard.resources.kotlinmodule.visitor.KotlinModulePackageVisitor; import java.util.*; import java.util.stream.Stream; /** * This class fixes the {@link KotlinModule} fileName to match the module name * and the strings referring to classes to match the obfuscated names. It also * moves the classes around into the correct package, if they were moved during * the obfuscation. * * @author James Hamilton */ public class KotlinModuleFixer implements ResourceFileVisitor { // Implementations for ResourceFileVisitor. @Override public void visitKotlinModule(KotlinModule kotlinModule) { // Fix the filename. kotlinModule.fileName = moduleNameToFileName(kotlinModule.name); // Visit each package part to fix the strings, and collect the existing package names. List existingPackageNames = new ArrayList<>(); kotlinModule.modulePackagesAccept(new ModulePackageNameFixer(existingPackageNames)); // Then collect all the files facades and multi-file parts to move Map fileFacadesToMove = new HashMap<>(); Map multiFilePartsToMove = new HashMap<>(); kotlinModule.modulePackagesAccept(new ToMoveCollector(fileFacadesToMove, multiFilePartsToMove)); // Then remove the parts to be moved from the current modules parts. kotlinModule.modulePackagesAccept(new ModulePartCleaner(fileFacadesToMove, multiFilePartsToMove)); // Then create the newly required module packages because // if something moved packages, a new package is now required. createNewModulePackages(kotlinModule, existingPackageNames, fileFacadesToMove, multiFilePartsToMove); // Finally, add the facades and parts to move into the correct modules. addFileFacadesToModule( kotlinModule, fileFacadesToMove); addMultiFilePartsToModule(kotlinModule, multiFilePartsToMove); } // Helper classes. private static class ModulePackageNameFixer implements KotlinModulePackageVisitor { private final List packageNames; ModulePackageNameFixer(List packageNames) { this.packageNames = packageNames; } @Override public void visitKotlinModulePackage(KotlinModule kotlinModule, KotlinModulePackage kotlinModulePackage) { packageNames.add(kotlinModulePackage.fqName); // First fix the names of file facades. List fileFacades = kotlinModulePackage.fileFacadeNames; for (int i = 0; i < fileFacades.size(); i++) { KotlinFileFacadeKindMetadata referencedFileFacade = kotlinModulePackage.referencedFileFacades.get(i); fileFacades.set(i, referencedFileFacade.ownerReferencedClass.getName()); } // Fix the names of multi-file parts. Map newMultiFileClassParts = new HashMap<>(); kotlinModulePackage.multiFileClassParts.forEach( (multiFilePartName, multiFileFacadeName) -> { KotlinMultiFilePartKindMetadata referencedMultiFilePart = kotlinModulePackage.referencedMultiFileParts.get(multiFilePartName); newMultiFileClassParts.put(referencedMultiFilePart.ownerClassName, referencedMultiFilePart.facadeName); if (!referencedMultiFilePart.ownerClassName.equals(multiFilePartName)) { kotlinModulePackage.referencedMultiFileParts.put(referencedMultiFilePart.ownerClassName, kotlinModulePackage.referencedMultiFileParts.get(multiFilePartName)); kotlinModulePackage.referencedMultiFileParts.remove(multiFilePartName); } }); kotlinModulePackage.multiFileClassParts.clear(); kotlinModulePackage.multiFileClassParts.putAll(newMultiFileClassParts); } } /** * Collects all the file facades and multi-file parts that are in the wrong packages. */ private static class ToMoveCollector implements KotlinModulePackageVisitor { private final Map fileFacadesToMove; private final Map multiFilePartsToMove; private ToMoveCollector(Map fileFacadesToMove, Map multiFilePartsToMove) { this.fileFacadesToMove = fileFacadesToMove; this.multiFilePartsToMove = multiFilePartsToMove; } @Override public void visitKotlinModulePackage(KotlinModule kotlinModule, KotlinModulePackage kotlinModulePart) { List fileFacadeNames = kotlinModulePart.fileFacadeNames; for (int i = 0; i < fileFacadeNames.size(); i++) { String fileFacadeName = fileFacadeNames.get(i); String fileFacadePackage = ClassUtil.internalPackageName(fileFacadeName); if (!fileFacadePackage.equals(kotlinModulePart.fqName)) { fileFacadesToMove.put(fileFacadeName, kotlinModulePart.referencedFileFacades.get(i)); } } kotlinModulePart.referencedMultiFileParts.forEach((multiFilePartName, referencedMultiFilePart) -> { if (!kotlinModulePart.fqName.equals(ClassUtil.internalPackageName(multiFilePartName))) { multiFilePartsToMove.put(multiFilePartName, referencedMultiFilePart); } }); } } /** * Remove the files facades and multi-file parts from the modules. */ private static class ModulePartCleaner implements KotlinModulePackageVisitor { private final Map fileFacadesToMove; private final Map multiFilePartsToMove; private ModulePartCleaner(Map fileFacadesToMove, Map multiFilePartsToMove) { this.fileFacadesToMove = fileFacadesToMove; this.multiFilePartsToMove = multiFilePartsToMove; } @Override public void visitKotlinModulePackage(KotlinModule kotlinModule, KotlinModulePackage kotlinModulePart) { for (String fileFacadeName : fileFacadesToMove.keySet()) { int index = kotlinModulePart.fileFacadeNames.indexOf(fileFacadeName); if (index != -1) { kotlinModulePart.fileFacadeNames .remove(index); kotlinModulePart.referencedFileFacades.remove(index); } } for (String multiFilePartName : multiFilePartsToMove.keySet()) { kotlinModulePart.multiFileClassParts.remove(multiFilePartName); kotlinModulePart.referencedMultiFileParts.remove(multiFilePartName); } } } // Helper methods for adding new modules. private static void createNewModulePackages(KotlinModule kotlinModule, List existingPackageNames, Map fileFacadesToMove, Map multiFilePartsToMove) { // Create a new package in the module for each of the classes packages // if they're not an already existing package. Stream.concat(fileFacadesToMove .keySet().stream(), multiFilePartsToMove.keySet().stream()) .map(ClassUtil::internalPackageName) .distinct() .filter(packageName -> !existingPackageNames.contains(packageName)) .map( packageName -> new KotlinModulePackage(packageName, new ArrayList<>(), new HashMap<>())) .forEach(kotlinModule.modulePackages::add); } private static void addFileFacadesToModule(KotlinModule kotlinModule, Map fileFacadesToMove) { fileFacadesToMove.forEach((fileFacadeName, referencedFileFacade) -> kotlinModule.modulePackagesAccept((__, modulePackage) -> { if (modulePackage.fqName.equals(ClassUtil.internalPackageName(fileFacadeName))) { modulePackage.fileFacadeNames .add(fileFacadeName); modulePackage.referencedFileFacades.add(referencedFileFacade); } })); } private static void addMultiFilePartsToModule(KotlinModule kotlinModule, Map multiFilePartsToMove) { multiFilePartsToMove.forEach((multiFilePartName, referencedMultiFilePart) -> kotlinModule.modulePackagesAccept((__, modulePackage) -> { if (modulePackage.fqName.equals(ClassUtil.internalPackageName(multiFilePartName))) { modulePackage.multiFileClassParts .put(multiFilePartName, referencedMultiFilePart.xs); modulePackage.referencedMultiFileParts.put(multiFilePartName, referencedMultiFilePart); } })); } // Small helper methods. private static String moduleNameToFileName(String moduleName) { return KotlinConstants.MODULE.FILE_EXPRESSION.replace("*", moduleName); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinModuleNameObfuscator.java ================================================ /* * 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.obfuscate.kotlin; import proguard.obfuscate.NameFactory; import proguard.resources.file.ResourceFile; import proguard.resources.file.visitor.*; import proguard.resources.kotlinmodule.KotlinModule; /** * Obfuscate module names using the given {@link NameFactory}. * * @author James Hamilton */ public class KotlinModuleNameObfuscator implements ResourceFileVisitor { private final NameFactory nameFactory; public KotlinModuleNameObfuscator(NameFactory nameFactory) { this.nameFactory = nameFactory; this.nameFactory.reset(); } // Implementations for ResourceFileVisitor. @Override public void visitKotlinModule(KotlinModule kotlinModule) { kotlinModule.name = nameFactory.nextName(); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinMultiFileFacadeFixer.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; import proguard.util.*; import static proguard.classfile.util.ClassUtil.internalPackagePrefix; import static proguard.classfile.util.ClassUtil.internalSimpleClassName; import static proguard.obfuscate.ClassObfuscator.*; /** * Ensure that multi-file class parts and multi-file facades are kept in the same package. * * @author James Hamilton */ public class KotlinMultiFileFacadeFixer implements KotlinMetadataVisitor { // Implementations for KotlinMetadataVisitor. @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinMultiFileFacadeMetadata(Clazz clazz, KotlinMultiFileFacadeKindMetadata kotlinMultiFileFacadeKindMetadata) { String packagePrefix = internalPackagePrefix(hasOriginalClassName(clazz) ? clazz.getName() : newClassName(clazz)); for (Clazz referencedPartClass : kotlinMultiFileFacadeKindMetadata.referencedPartClasses) { if (dontObfuscate(referencedPartClass)) { packagePrefix = internalPackagePrefix(referencedPartClass.getName()); break; } } for (Clazz ref : kotlinMultiFileFacadeKindMetadata.referencedPartClasses) { setNewClassName(ref, packagePrefix + (internalSimpleClassName(hasOriginalClassName(ref) ? ref.getName() : newClassName(ref)))); } String className = newClassName(clazz); if (className == null) { className = clazz.getName(); } setNewClassName(clazz, packagePrefix + internalSimpleClassName(className)); } // Small helper methods. private static boolean dontObfuscate(Processable processable) { return (processable.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinObjectFixer.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.*; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; import proguard.classfile.visitor.MemberVisitor; import static proguard.classfile.kotlin.KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME; import static proguard.obfuscate.MemberObfuscator.setNewMemberName; /** * This fixer ensures that the INSTANCE field in an object class remains named INSTANCE. * * @author James Hamilton */ public class KotlinObjectFixer implements KotlinMetadataVisitor { @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { if (kotlinClassKindMetadata.flags.isObject) { clazz.fieldAccept(KOTLIN_OBJECT_INSTANCE_FIELD_NAME, null, new MemberVisitor() { @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { setNewMemberName(programField, KOTLIN_OBJECT_INSTANCE_FIELD_NAME); } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) {} @Override public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {} @Override public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {} } ); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinPropertyNameObfuscator.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.*; import proguard.obfuscate.NameFactory; import proguard.util.ProcessingFlags; public class KotlinPropertyNameObfuscator implements KotlinMetadataVisitor, // Implementation interfaces. KotlinPropertyVisitor { private final NameFactory nameFactory; public KotlinPropertyNameObfuscator(NameFactory nameFactory) { this.nameFactory = nameFactory; } // Implementations for KotlinMetadataVisitor. @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) { this.nameFactory.reset(); kotlinDeclarationContainerMetadata.propertiesAccept(clazz, this); } // Implementations for KotlinPropertyVisitor. @Override public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata) { // Don't rename a property if its backing field or // any accessor are explicitly kept. if ((kotlinPropertyMetadata.referencedBackingField != null && (kotlinPropertyMetadata.referencedBackingField.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0) || (kotlinPropertyMetadata.referencedGetterMethod != null && (kotlinPropertyMetadata.referencedGetterMethod.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0) || (kotlinPropertyMetadata.referencedSetterMethod != null && (kotlinPropertyMetadata.referencedSetterMethod.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0)) { return; } kotlinPropertyMetadata.setProcessingInfo(nameFactory.nextName()); } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinPropertyRenamer.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.KotlinPropertyVisitor; /** * @author James Hamilton */ public class KotlinPropertyRenamer implements KotlinPropertyVisitor { @Override public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata) { if (kotlinPropertyMetadata.getProcessingInfo() != null) { String originalName = kotlinPropertyMetadata.name; String newName = (String)kotlinPropertyMetadata.getProcessingInfo(); if (!originalName.equals(newName)) { kotlinPropertyMetadata.name = newName; } } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinSourceDebugExtensionAttributeObfuscator.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; /** * This class sets the SourceDebugExtension attribute to a basic minimum * SMAP entry. See DGD-1417. */ public class KotlinSourceDebugExtensionAttributeObfuscator implements AttributeVisitor { private static final String MINIMUM_SMAP = "SMAP\n" + // SMAP Header. "\n" + // Name of generated file (blank). "Kotlin\n" + // Default stratum name. "*S Kotlin\n" + // Kotlin stratum. "*F\n" + // File section. "+ 1 \n" + // File ID 1, blank name. "\n" + // File ID 1 source path. "*L\n" + // Lines section. "1#1,1:1\n" + // File 1#line 1,repeatCount:outputStartLine "*E"; // End. @Override public void visitSourceDebugExtensionAttribute(Clazz clazz, SourceDebugExtensionAttribute sourceDebugExtensionAttribute) { sourceDebugExtensionAttribute.info = MINIMUM_SMAP.getBytes(); sourceDebugExtensionAttribute.u4attributeLength = sourceDebugExtensionAttribute.info.length; } @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinSyntheticClassFixer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.obfuscate.kotlin; import proguard.classfile.*; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.*; import proguard.classfile.visitor.*; import static proguard.classfile.kotlin.KotlinConstants.*; import static proguard.obfuscate.ClassObfuscator.*; import static proguard.obfuscate.MemberObfuscator.*; /** * Synthetic classes are created for lambdas, $DefaultImpls and $WhenMappings. * * This class ensures $DefaultImpls and $WhenMappings classes are correctly named * and lambda classes have their $field prefixed with a $. */ public class KotlinSyntheticClassFixer implements KotlinMetadataVisitor { @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { // If there is a default implementations class, name it as this one but with a $DefaultImpls suffix. if (kotlinClassKindMetadata.referencedDefaultImplsClass != null) { String className = newClassName(kotlinClassKindMetadata.referencedClass); final String defaultImplsClassName = className.endsWith(DEFAULT_IMPLEMENTATIONS_SUFFIX) ? className : className + DEFAULT_IMPLEMENTATIONS_SUFFIX; kotlinClassKindMetadata.accept(clazz, new KotlinClassToDefaultImplsClassVisitor( new ProgramClassFilter(_clazz -> setNewClassName(_clazz, defaultImplsClassName)))); } } @Override public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassKindMetadata kotlinSyntheticClassKindMetadata) { // If this is a $WhenMappings class ensure that it has the suffix. if (kotlinSyntheticClassKindMetadata.flavor == KotlinSyntheticClassKindMetadata.Flavor.WHEN_MAPPINGS) { String originalName = newClassName(clazz); if (!originalName.endsWith(WHEN_MAPPINGS_SUFFIX)) { setNewClassName(clazz, originalName + WHEN_MAPPINGS_SUFFIX); } } else if (kotlinSyntheticClassKindMetadata.flavor == KotlinSyntheticClassKindMetadata.Flavor.LAMBDA) { clazz.accept( // Obfuscate the lambda fields in synthetic lambda classes, but ensuring $ prefix is kept. new AllFieldVisitor( new MemberNameFilter("$*", new MemberVisitor() { @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { setNewMemberName(programField, "$" + newMemberName(programField)); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) {} public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {} public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {} } )) ); } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinSyntheticToStringObfuscator.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.Constant; import proguard.classfile.constant.StringConstant; import proguard.classfile.constant.Utf8Constant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.kotlin.KotlinClassKindMetadata; import proguard.classfile.kotlin.KotlinDeclarationContainerMetadata; import proguard.classfile.kotlin.KotlinMetadata; import proguard.classfile.kotlin.KotlinPropertyMetadata; import proguard.classfile.kotlin.visitor.KotlinFunctionToMethodVisitor; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.KotlinPropertyVisitor; import proguard.classfile.kotlin.visitor.filter.KotlinFunctionFilter; import proguard.obfuscate.ClassObfuscator; import proguard.strip.KotlinAnnotationStripper; import proguard.util.kotlin.asserter.KotlinMetadataAsserter; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; import java.util.function.Function; import static proguard.classfile.ClassConstants.METHOD_NAME_TOSTRING; import static proguard.classfile.ClassConstants.METHOD_NAME_TOSTRING_IMPL; import static proguard.classfile.ClassConstants.METHOD_TYPE_TOSTRING; import static proguard.classfile.ClassConstants.METHOD_TYPE_TOSTRING_IMPL; import static proguard.classfile.util.ClassUtil.internalSimpleClassName; import static proguard.obfuscate.ClassObfuscator.hasOriginalClassName; import static proguard.obfuscate.ClassObfuscator.newClassName; /** * Some types of Kotlin classes (i.e. data, value, inline) simply hold data. * The compiler uses this fact to automatically generate functions that a programmer would normally implement manually. * * One of these functions is the default toString() and its potential implementation counterpart toString-impl(). * The output of these functions is of the form "User(name=John, age=42)". * * This exposes unobfuscated classNames (in the example above "User") so we must update these className strings * with their obfuscated versions. * * This class relies on the {@link KotlinPropertyNameObfuscator} storing the obfuscated * name in the processingInfo field. And that the {@link ClassObfuscator} gives us the * new class name (internally this also relies on the new class name being in the * processingInfo field). * * One limitation of this class is that it requires that the Kotlin metadata is still attached to the * classes that are processed. If the Kotlin metadata is stripped {@link KotlinAnnotationStripper} or * removed by the {@link KotlinMetadataAsserter} the `toString` method's output cannot be updated with the * obfuscated class name. */ public class KotlinSyntheticToStringObfuscator implements KotlinMetadataVisitor { private static final Comparator REVERSE_LENGTH_STRING_ORDER = Comparator .comparingInt(String::length) .reversed() .thenComparing(Function.identity()); // Implementations for KotlinMetadataVisitor. @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { // Retrieve all the property names and their obfuscated versions. // Start with the longest strings, in-case the smaller strings appear within longer ones. Map nameMap = new TreeMap<>(REVERSE_LENGTH_STRING_ORDER); kotlinClassKindMetadata.propertiesAccept(clazz, new PropertyNameCollector(nameMap)); // Add the original class name/obfuscated class name to the map as well // but add "(" to class name to differentiate in-case the class is named the same as a property. if (!hasOriginalClassName(clazz)) { nameMap.put(internalSimpleClassName(kotlinClassKindMetadata.className) + "(", internalSimpleClassName(newClassName(clazz)) + "("); } // We visit all the ldc instructions in the automatically declared toString function // and use the nameMap to replace string values. kotlinClassKindMetadata.functionsAccept(clazz, new KotlinFunctionFilter( fun -> !fun.flags.isDeclaration && (fun.name.equals(METHOD_NAME_TOSTRING) || fun.name.equals(METHOD_NAME_TOSTRING_IMPL)) && (fun.jvmSignature.descriptor.toString().equals(METHOD_TYPE_TOSTRING) || fun.jvmSignature.descriptor.toString().equals(METHOD_TYPE_TOSTRING_IMPL)), new KotlinFunctionToMethodVisitor( new AllAttributeVisitor( new MyObfuscatedToStringFixer(nameMap, new ConstantPoolEditor((ProgramClass)clazz)))))); } private static final class MyObfuscatedToStringFixer implements AttributeVisitor, InstructionVisitor, ConstantVisitor { private final Map originalToNewName; private final ConstantPoolEditor constantPoolEditor; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); private String replacement = null; private MyObfuscatedToStringFixer(Map originalToNewName, ConstantPoolEditor constantPoolEditor) { this.originalToNewName = originalToNewName; this.constantPoolEditor = constantPoolEditor; } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttributeEditor.reset(codeAttribute.u4codeLength); codeAttribute.instructionsAccept(clazz, method, this); codeAttribute.accept(clazz, method, codeAttributeEditor); } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_LDC || constantInstruction.opcode == Instruction.OP_LDC_W) { replacement = null; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); if (replacement != null) { constantInstruction.constantIndex = constantPoolEditor.addStringConstant(replacement); codeAttributeEditor.replaceInstruction(offset, constantInstruction); } } } // Implementations for ConstantVisitor. @Override public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { clazz.constantPoolEntryAccept(stringConstant.u2stringIndex, this); } @Override public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { Map copy = new TreeMap<>(originalToNewName); boolean foundAny = false; replacement = utf8Constant.getString(); for (String originalName : copy.keySet()) { if (replacement.contains(originalName)) { replacement = replacement.replace(originalName, copy.get(originalName)); foundAny = true; originalToNewName.remove(originalName); } } if (!foundAny) { replacement = null; } } @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} } // Collect the original name/new name map - assumes the old name is in the processingInfo. private static final class PropertyNameCollector implements KotlinPropertyVisitor { private final Map nameMap; private PropertyNameCollector(Map nameMap) { this.nameMap = nameMap; } // Implementations for KotlinPropertyVisitor. @Override public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata) { if (kotlinPropertyMetadata.getProcessingInfo() != null) { this.nameMap.put(kotlinPropertyMetadata.name + "=", kotlinPropertyMetadata.getProcessingInfo() + "="); } } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinUnsupportedExceptionReplacementSequences.java ================================================ /* * 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.obfuscate.kotlin; import proguard.classfile.ClassPool; import proguard.classfile.constant.Constant; import proguard.classfile.editor.InstructionSequenceBuilder; import proguard.classfile.instruction.Instruction; import proguard.classfile.util.InstructionSequenceMatcher; import proguard.obfuscate.util.ReplacementSequences; import static proguard.classfile.ClassConstants.NAME_JAVA_LANG_UNSUPPORTED_OP_EXCEPTION; /** * The Kotlin compiler automatically inserts a null check into $default methods, * checking the last parameter but the string contains the original name of the * method. So here we remove the string. */ public final class KotlinUnsupportedExceptionReplacementSequences implements ReplacementSequences { private static final int CONSTANT_INDEX_1 = InstructionSequenceMatcher.X; private static final int ALOAD_INDEX = InstructionSequenceMatcher.A; private final Instruction[][][] SEQUENCES; private final Constant[] CONSTANTS; public KotlinUnsupportedExceptionReplacementSequences(ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); SEQUENCES = new Instruction[][][] { { ____ .aload(ALOAD_INDEX) .ifnull(InstructionSequenceMatcher.Y) .new_(NAME_JAVA_LANG_UNSUPPORTED_OP_EXCEPTION) .dup() .ldc_(CONSTANT_INDEX_1) .invokespecial(NAME_JAVA_LANG_UNSUPPORTED_OP_EXCEPTION, "", "(Ljava/lang/String;)V").__(), ____ .aload(ALOAD_INDEX) .ifnull(InstructionSequenceMatcher.Y) .new_(NAME_JAVA_LANG_UNSUPPORTED_OP_EXCEPTION) .dup() .invokespecial(NAME_JAVA_LANG_UNSUPPORTED_OP_EXCEPTION, "", "()V").__() }, }; CONSTANTS = ____.constants(); } @Override public Instruction[][][] getSequences() { return SEQUENCES; } @Override public Constant[] getConstants() { return CONSTANTS; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinValueParameterNameShrinker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.*; /** * This KotlinValueParameterVisitor removes the name of ValueParameters that it visits, based on the markings of the * {@link KotlinValueParameterUsageMarker}. */ public class KotlinValueParameterNameShrinker implements KotlinMetadataVisitor, KotlinConstructorVisitor, KotlinPropertyVisitor, KotlinFunctionVisitor { // Implementations for KotlinMetadataVisitor. @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) { kotlinDeclarationContainerMetadata.functionsAccept(clazz, this); kotlinDeclarationContainerMetadata.propertiesAccept(clazz,this); } @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { kotlinClassKindMetadata.constructorsAccept(clazz, this); visitKotlinDeclarationContainerMetadata(clazz, kotlinClassKindMetadata); } @Override public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassKindMetadata kotlinSyntheticClassKindMetadata) { kotlinSyntheticClassKindMetadata.functionsAccept(clazz, this); } // Implementations for KotlinConstructorVisitor. @Override public void visitConstructor(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata, KotlinConstructorMetadata kotlinConstructorMetadata) { kotlinConstructorMetadata.valueParametersAccept(clazz, kotlinClassKindMetadata, new MyValueParameterShrinker()); } // Implementations for KotlinPropertyVisitor. @Override public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata) { kotlinPropertyMetadata.setterParametersAccept(clazz, kotlinDeclarationContainerMetadata, new MyValueParameterShrinker()); } // Implementations for KotlinFunctionVisitor. @Override public void visitAnyFunction(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { kotlinFunctionMetadata.valueParametersAccept(clazz, kotlinMetadata, new MyValueParameterShrinker()); } private static class MyValueParameterShrinker implements KotlinValueParameterVisitor { private int parameterNumber = 0; @Override public void visitAnyValueParameter(Clazz clazz, KotlinValueParameterMetadata kotlinValueParameterMetadata) { if (!KotlinValueParameterUsageMarker.isUsed(kotlinValueParameterMetadata)) { kotlinValueParameterMetadata.parameterName = "p" + parameterNumber++; } } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/kotlin/KotlinValueParameterUsageMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.obfuscate.kotlin; import proguard.classfile.Clazz; import proguard.classfile.Member; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramMethod; import proguard.classfile.kotlin.KotlinClassKindMetadata; import proguard.classfile.kotlin.KotlinConstructorMetadata; import proguard.classfile.kotlin.KotlinDeclarationContainerMetadata; import proguard.classfile.kotlin.KotlinFunctionMetadata; import proguard.classfile.kotlin.KotlinMetadata; import proguard.classfile.kotlin.KotlinPropertyMetadata; import proguard.classfile.kotlin.KotlinSyntheticClassKindMetadata; import proguard.classfile.kotlin.KotlinValueParameterMetadata; import proguard.classfile.kotlin.visitor.AllPropertyVisitor; import proguard.classfile.kotlin.visitor.KotlinConstructorVisitor; import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.KotlinPropertyVisitor; import proguard.classfile.kotlin.visitor.KotlinValueParameterVisitor; import proguard.classfile.visitor.MemberVisitor; import proguard.util.Processable; import static proguard.util.ProcessingFlags.DONT_OBFUSCATE; /** * This KotlinMetadataVisitor marks ValueParameters of constructors, properties and functions * if their referenced method is not obfuscated. */ public class KotlinValueParameterUsageMarker implements KotlinMetadataVisitor, // Implementation interfaces. KotlinConstructorVisitor, KotlinPropertyVisitor, KotlinFunctionVisitor, MemberVisitor, KotlinValueParameterVisitor { // A processing info flag to indicate the attribute is being used. private static final Object USED = new Object(); private boolean keepParameterInfo; // Implementations for KotlinMetadataVisitor. @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) { kotlinDeclarationContainerMetadata.functionsAccept(clazz, this); kotlinDeclarationContainerMetadata.accept(clazz, new AllPropertyVisitor(this)); } @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { kotlinClassKindMetadata.constructorsAccept(clazz, this); visitKotlinDeclarationContainerMetadata(clazz, kotlinClassKindMetadata); } @Override public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassKindMetadata kotlinSyntheticClassKindMetadata) { kotlinSyntheticClassKindMetadata.functionsAccept(clazz, this); } // Implementations for KotlinConstructorVisitor. @Override public void visitConstructor(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata, KotlinConstructorMetadata kotlinConstructorMetadata) { keepParameterInfo = false; kotlinConstructorMetadata.referencedMethodAccept(clazz, this); if (keepParameterInfo) { kotlinConstructorMetadata.valueParametersAccept(clazz, kotlinClassKindMetadata, this); } } // Implementations for KotlinPropertyVisitor. @Override public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata) { keepParameterInfo = false; if (kotlinPropertyMetadata.referencedSetterMethod != null) { kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, this); } if (keepParameterInfo) { kotlinPropertyMetadata.setterParametersAccept(clazz, kotlinDeclarationContainerMetadata, this); } } // Implementations for KotlinFunctionVisitor. @Override public void visitAnyFunction(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { keepParameterInfo = false; kotlinFunctionMetadata.referencedMethodAccept(this); if (keepParameterInfo) { kotlinFunctionMetadata.valueParametersAccept(clazz, kotlinMetadata, this); } } // Implementations for MemberVisitor @Override public void visitAnyMember(Clazz clazz, Member member) {} @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { keepParameterInfo = (programMethod.getProcessingFlags() & DONT_OBFUSCATE) != 0; } // Implementations for KotlinValueParameterVisitor. @Override public void visitAnyValueParameter(Clazz clazz, KotlinValueParameterMetadata kotlinValueParameterMetadata) { markAsUsed(kotlinValueParameterMetadata); } // Small utility methods. /** * Marks the given Processable as being used (or useful). * In this context, the Processable will be a KotlinValueParameter object. */ private static void markAsUsed(Processable processable) { processable.setProcessingInfo(USED); } /** * Returns whether the given Processable has been marked as being used. * In this context, the Processable will be a KotlinValueParameter object. */ static boolean isUsed(Processable processable) { return processable.getProcessingInfo() == USED; } } ================================================ FILE: base/src/main/java/proguard/obfuscate/obfuscate/package.html ================================================ This package contains classes to perform obfuscation of class files. ================================================ FILE: base/src/main/java/proguard/obfuscate/util/InstructionSequenceObfuscator.java ================================================ /* * 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.obfuscate.util; import proguard.classfile.*; import proguard.classfile.attribute.visitor.AttributeProcessingFlagFilter; import proguard.classfile.constant.Constant; import proguard.classfile.editor.*; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.*; import proguard.classfile.util.BranchTargetFinder; import proguard.classfile.visitor.*; import proguard.util.ProcessingFlags; import java.util.Arrays; public class InstructionSequenceObfuscator implements ClassVisitor, MemberVisitor { private final PeepholeEditor peepholeEditor; public InstructionSequenceObfuscator(ReplacementSequences replacementSequences) { BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); peepholeEditor = new PeepholeEditor( branchTargetFinder, codeAttributeEditor, new MyInstructionSequenceReplacer( replacementSequences.getConstants(), replacementSequences.getSequences(), branchTargetFinder, codeAttributeEditor )); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { programClass.methodsAccept(this); } // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) { } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { programMethod.attributesAccept( programClass, new AttributeProcessingFlagFilter(0, ProcessingFlags.DONT_OBFUSCATE, peepholeEditor)); } // Helper classes. private static class MyInstructionSequenceReplacer extends MultiInstructionVisitor { MyInstructionSequenceReplacer(Constant[] constants, Instruction[][][] insSequences, BranchTargetFinder branchTargetFinder, CodeAttributeEditor codeAttributeEditor) { super(createInstructionSequenceReplacers(constants, insSequences, branchTargetFinder, codeAttributeEditor)); } private static InstructionVisitor[] createInstructionSequenceReplacers(Constant[] constants, Instruction[][][] insSequences, BranchTargetFinder branchTargetFinder, CodeAttributeEditor codeAttributeEditor) { InstructionVisitor[] isReplacers = new InstructionSequenceReplacer[insSequences.length]; Arrays.setAll( isReplacers, index -> new InstructionSequenceReplacer(constants, insSequences[index][0], constants, insSequences[index][1], branchTargetFinder, codeAttributeEditor, null) ); return isReplacers; } } } ================================================ FILE: base/src/main/java/proguard/obfuscate/util/ReplacementSequences.java ================================================ /* * 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.obfuscate.util; import proguard.classfile.constant.Constant; import proguard.classfile.instruction.Instruction; public interface ReplacementSequences { Instruction[][][] getSequences(); Constant[] getConstants(); } ================================================ FILE: base/src/main/java/proguard/optimize/BootstrapMethodArgumentShrinker.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.attribute.BootstrapMethodInfo; import proguard.classfile.attribute.visitor.BootstrapMethodInfoVisitor; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.info.*; import proguard.optimize.peephole.VariableShrinker; /** * This BootstrapMethodInfoVisitor removes unused constant arguments from * bootstrap method entries that it visits. * * @see ParameterUsageMarker * @see VariableUsageMarker * @see VariableShrinker * @author Eric Lafortune */ public class BootstrapMethodArgumentShrinker implements BootstrapMethodInfoVisitor, ConstantVisitor, MemberVisitor { private long usedParameters; // Implementations for BootstrapMethodInfoVisitor. public void visitBootstrapMethodInfo(Clazz clazz, BootstrapMethodInfo bootstrapMethodInfo) { // Check which method parameters are used. usedParameters = -1L; clazz.constantPoolEntryAccept(bootstrapMethodInfo.u2methodHandleIndex, this); // Remove the unused arguments. int methodArgumentCount = bootstrapMethodInfo.u2methodArgumentCount; int[] methodArguments = bootstrapMethodInfo.u2methodArguments; int newArgumentIndex = 0; for (int argumentIndex = 0; argumentIndex < methodArgumentCount; argumentIndex++) { if (argumentIndex >= 64 || (usedParameters & (1L << argumentIndex)) != 0L) { methodArguments[newArgumentIndex++] = methodArguments[argumentIndex]; } } // Update the number of arguments. bootstrapMethodInfo.u2methodArgumentCount = newArgumentIndex; } // Implementations for ConstantVisitor. public void visitMethodHandleConstant(Clazz clazz, MethodHandleConstant methodHandleConstant) { // Check the referenced bootstrap method. clazz.constantPoolEntryAccept(methodHandleConstant.u2referenceIndex, this); } public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { // Check the referenced class member itself. refConstant.referencedMemberAccept(this); } // Implementations for MemberVisitor. public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { usedParameters = ParameterUsageMarker.getUsedParameters(programMethod); } } ================================================ FILE: base/src/main/java/proguard/optimize/CalledMemberVisitor.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.*; /** * This InstructionVisitor visits all members that can be executed because of an instruction. * Explicitly (invoke instructions) or implicitly (class initialisation methods) */ public class CalledMemberVisitor implements InstructionVisitor { private final MemberVisitor memberVisitor; private final MemberToClassVisitor staticClassInitializer; public CalledMemberVisitor(MemberVisitor memberVisitor) { this.memberVisitor = memberVisitor; this.staticClassInitializer = new MemberToClassVisitor( new NamedMethodVisitor(ClassConstants.METHOD_NAME_CLINIT, null, memberVisitor)); } // Implementations for InstructionVisitor public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: case Instruction.OP_INVOKEINTERFACE: case Instruction.OP_INVOKEDYNAMIC: clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ReferencedMemberVisitor( new MultiMemberVisitor(memberVisitor,staticClassInitializer))); break; case Instruction.OP_GETSTATIC: case Instruction.OP_PUTSTATIC: clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ReferencedMemberVisitor(staticClassInitializer)); break; } } } ================================================ FILE: base/src/main/java/proguard/optimize/ChangedCodePrinter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.module.*; import proguard.classfile.attribute.preverification.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.util.ClassUtil; /** * This AttributeVisitor delegates its call to another AttributeVisitor, and * prints out the code if the other visitor has changed it. * * @author Eric Lafortune */ public class ChangedCodePrinter implements AttributeVisitor { private static final Logger logger = LogManager.getLogger(ChangedCodePrinter.class); private final AttributeVisitor attributeVisitor; public ChangedCodePrinter(AttributeVisitor attributeVisitor) { this.attributeVisitor = attributeVisitor; } // Implementations for AttributeVisitor. public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute) { attributeVisitor.visitUnknownAttribute(clazz, unknownAttribute); } public void visitSourceDebugExtensionAttribute(Clazz clazz, SourceDebugExtensionAttribute sourceDebugExtensionAttribute) { attributeVisitor.visitSourceDebugExtensionAttribute(clazz, sourceDebugExtensionAttribute); } public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { attributeVisitor.visitBootstrapMethodsAttribute(clazz, bootstrapMethodsAttribute); } public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute) { attributeVisitor.visitSourceFileAttribute(clazz, sourceFileAttribute); } public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute) { attributeVisitor.visitSourceDirAttribute(clazz, sourceDirAttribute); } public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { attributeVisitor.visitInnerClassesAttribute(clazz, innerClassesAttribute); } public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) { attributeVisitor.visitEnclosingMethodAttribute(clazz, enclosingMethodAttribute); } public void visitNestHostAttribute(Clazz clazz, NestHostAttribute nestHostAttribute) { attributeVisitor.visitNestHostAttribute(clazz, nestHostAttribute); } public void visitNestMembersAttribute(Clazz clazz, NestMembersAttribute nestMembersAttribute) { attributeVisitor.visitNestMembersAttribute(clazz, nestMembersAttribute); } public void visitPermittedSubclassesAttribute(Clazz clazz, PermittedSubclassesAttribute permittedSubclassesAttribute) { attributeVisitor.visitPermittedSubclassesAttribute(clazz, permittedSubclassesAttribute); } public void visitModuleAttribute(Clazz clazz, ModuleAttribute moduleAttribute) { attributeVisitor.visitModuleAttribute(clazz, moduleAttribute); } public void visitModuleMainClassAttribute(Clazz clazz, ModuleMainClassAttribute moduleMainClassAttribute) { attributeVisitor.visitModuleMainClassAttribute(clazz, moduleMainClassAttribute); } public void visitModulePackagesAttribute(Clazz clazz, ModulePackagesAttribute modulePackagesAttribute) { attributeVisitor.visitModulePackagesAttribute(clazz, modulePackagesAttribute); } public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute) { attributeVisitor.visitDeprecatedAttribute(clazz, deprecatedAttribute); } public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute) { attributeVisitor.visitSyntheticAttribute(clazz, syntheticAttribute); } public void visitSignatureAttribute(Clazz clazz, SignatureAttribute syntheticAttribute) { attributeVisitor.visitSignatureAttribute(clazz, syntheticAttribute); } public void visitDeprecatedAttribute(Clazz clazz, Field field, DeprecatedAttribute deprecatedAttribute) { attributeVisitor.visitDeprecatedAttribute(clazz, field, deprecatedAttribute); } public void visitSyntheticAttribute(Clazz clazz, Field field, SyntheticAttribute syntheticAttribute) { attributeVisitor.visitSyntheticAttribute(clazz, field, syntheticAttribute); } public void visitSignatureAttribute(Clazz clazz, Field field, SignatureAttribute syntheticAttribute) { attributeVisitor.visitSignatureAttribute(clazz, field, syntheticAttribute); } public void visitDeprecatedAttribute(Clazz clazz, Method method, DeprecatedAttribute deprecatedAttribute) { attributeVisitor.visitDeprecatedAttribute(clazz, method, deprecatedAttribute); } public void visitSyntheticAttribute(Clazz clazz, Method method, SyntheticAttribute syntheticAttribute) { attributeVisitor.visitSyntheticAttribute(clazz, method, syntheticAttribute); } public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute syntheticAttribute) { attributeVisitor.visitSignatureAttribute(clazz, method, syntheticAttribute); } public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute) { attributeVisitor.visitConstantValueAttribute(clazz, field, constantValueAttribute); } public void visitMethodParametersAttribute(Clazz clazz, Method method, MethodParametersAttribute exceptionsAttribute) { attributeVisitor.visitMethodParametersAttribute(clazz, method, exceptionsAttribute); } public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute) { attributeVisitor.visitExceptionsAttribute(clazz, method, exceptionsAttribute); } public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute) { attributeVisitor.visitStackMapAttribute(clazz, method, codeAttribute, stackMapAttribute); } public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute) { attributeVisitor.visitStackMapTableAttribute(clazz, method, codeAttribute, stackMapTableAttribute); } public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) { attributeVisitor.visitLineNumberTableAttribute(clazz, method, codeAttribute, lineNumberTableAttribute); } public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { attributeVisitor.visitLocalVariableTableAttribute(clazz, method, codeAttribute, localVariableTableAttribute); } public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { attributeVisitor.visitLocalVariableTypeTableAttribute(clazz, method, codeAttribute, localVariableTypeTableAttribute); } public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute) { attributeVisitor.visitRuntimeVisibleAnnotationsAttribute(clazz, runtimeVisibleAnnotationsAttribute); } public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute) { attributeVisitor.visitRuntimeInvisibleAnnotationsAttribute(clazz, runtimeInvisibleAnnotationsAttribute); } public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute) { attributeVisitor.visitRuntimeVisibleAnnotationsAttribute(clazz, field, runtimeVisibleAnnotationsAttribute); } public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute) { attributeVisitor.visitRuntimeInvisibleAnnotationsAttribute(clazz, field, runtimeInvisibleAnnotationsAttribute); } public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute) { attributeVisitor.visitRuntimeVisibleAnnotationsAttribute(clazz, method, runtimeVisibleAnnotationsAttribute); } public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute) { attributeVisitor.visitRuntimeInvisibleAnnotationsAttribute(clazz, method, runtimeInvisibleAnnotationsAttribute); } public void visitRuntimeVisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute) { attributeVisitor.visitRuntimeVisibleParameterAnnotationsAttribute(clazz, method, runtimeVisibleParameterAnnotationsAttribute); } public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute) { attributeVisitor.visitRuntimeInvisibleParameterAnnotationsAttribute(clazz, method, runtimeInvisibleParameterAnnotationsAttribute); } public void visitRuntimeVisibleTypeAnnotationsAttribute(Clazz clazz, RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute) { attributeVisitor.visitRuntimeVisibleTypeAnnotationsAttribute(clazz, runtimeVisibleTypeAnnotationsAttribute); } public void visitRuntimeVisibleTypeAnnotationsAttribute(Clazz clazz, Field field, RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute) { attributeVisitor.visitRuntimeVisibleTypeAnnotationsAttribute(clazz, field, runtimeVisibleTypeAnnotationsAttribute); } public void visitRuntimeVisibleTypeAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute) { attributeVisitor.visitRuntimeVisibleTypeAnnotationsAttribute(clazz, method, runtimeVisibleTypeAnnotationsAttribute); } public void visitRuntimeVisibleTypeAnnotationsAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute) { attributeVisitor.visitRuntimeVisibleTypeAnnotationsAttribute(clazz, method, codeAttribute, runtimeVisibleTypeAnnotationsAttribute); } public void visitRuntimeInvisibleTypeAnnotationsAttribute(Clazz clazz, RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute) { attributeVisitor.visitRuntimeInvisibleTypeAnnotationsAttribute(clazz, runtimeInvisibleTypeAnnotationsAttribute); } public void visitRuntimeInvisibleTypeAnnotationsAttribute(Clazz clazz, Field field, RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute) { attributeVisitor.visitRuntimeInvisibleTypeAnnotationsAttribute(clazz, field, runtimeInvisibleTypeAnnotationsAttribute); } public void visitRuntimeInvisibleTypeAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute) { attributeVisitor.visitRuntimeInvisibleTypeAnnotationsAttribute(clazz, method, runtimeInvisibleTypeAnnotationsAttribute); } public void visitRuntimeInvisibleTypeAnnotationsAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute) { attributeVisitor.visitRuntimeInvisibleTypeAnnotationsAttribute(clazz, method, codeAttribute, runtimeInvisibleTypeAnnotationsAttribute); } public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) { attributeVisitor.visitAnnotationDefaultAttribute(clazz, method, annotationDefaultAttribute); } public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { byte[] code = codeAttribute.code; byte[] oldCode = new byte[code.length]; // Copy the current code. System.arraycopy(code, 0, oldCode, 0, codeAttribute.u4codeLength); // Delegate to the real visitor. attributeVisitor.visitCodeAttribute(clazz, method, codeAttribute); // Check if the code has changed. if (codeHasChanged(codeAttribute, oldCode)) { printChangedCode(clazz, method, codeAttribute, oldCode); } } // Small utility methods. private boolean codeHasChanged(CodeAttribute codeAttribute, byte[] oldCode) { if (oldCode.length != codeAttribute.u4codeLength) { return true; } for (int index = 0; index < codeAttribute.u4codeLength; index++) { if (oldCode[index] != codeAttribute.code[index]) { return true; } } return false; } private void printChangedCode(Clazz clazz, Method method, CodeAttribute codeAttribute, byte[] oldCode) { logger.info("Class {}", ClassUtil.externalClassName(clazz.getName())); logger.info("Method {}", ClassUtil.externalFullMethodDescription(clazz.getName(), 0, method.getName(clazz), method.getDescriptor(clazz))); for (int index = 0; index < codeAttribute.u4codeLength; index++) { logger.info("{}{}: {} {}", oldCode[index] == codeAttribute.code[index]? " -- ":" => ", index, Integer.toHexString(0x100|oldCode[index] &0xff).substring(1), Integer.toHexString(0x100|codeAttribute.code[index]&0xff).substring(1) ); } } } ================================================ FILE: base/src/main/java/proguard/optimize/ConstantMemberFilter.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.value.Value; import proguard.optimize.evaluation.StoringInvocationUnit; /** * This MemberVisitor delegates its visits to program class members * to another given MemberVisitor, but only when the visited * class member has been marked as a constant. * * @see StoringInvocationUnit * @author Eric Lafortune */ public class ConstantMemberFilter implements MemberVisitor { private final MemberVisitor constantMemberVisitor; /** * Creates a new ConstantMemberFilter. * @param constantMemberVisitor the MemberVisitor to which * visits to constant members will be delegated. */ public ConstantMemberFilter(MemberVisitor constantMemberVisitor) { this.constantMemberVisitor = constantMemberVisitor; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { Value value = StoringInvocationUnit.getFieldValue(programField); if (value != null && value.isParticular()) { constantMemberVisitor.visitProgramField(programClass, programField); } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { Value value = StoringInvocationUnit.getMethodReturnValue(programMethod); if (value != null && value.isParticular()) { constantMemberVisitor.visitProgramMethod(programClass, programMethod); } } } ================================================ FILE: base/src/main/java/proguard/optimize/ConstantParameterFilter.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.value.Value; import proguard.optimize.evaluation.StoringInvocationUnit; /** * This MemberVisitor delegates its visits to program methods * to another given MemberVisitor, for each method parameter * that has been marked as constant. * * @see StoringInvocationUnit * @author Eric Lafortune */ public class ConstantParameterFilter implements MemberVisitor { private final MemberVisitor constantParameterVisitor; /** * Creates a new ConstantParameterFilter. * @param constantParameterVisitor the MemberVisitor to which * visits will be delegated. */ public ConstantParameterFilter(MemberVisitor constantParameterVisitor) { this.constantParameterVisitor = constantParameterVisitor; } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // All parameters of non-static methods are shifted by one in the local // variable frame. boolean isStatic = (programMethod.getAccessFlags() & AccessConstants.STATIC) != 0; int parameterStart = isStatic ? 0 : 1; int parameterCount = ClassUtil.internalMethodParameterCount(programMethod.getDescriptor(programClass), isStatic); for (int index = parameterStart; index < parameterCount; index++) { Value value = StoringInvocationUnit.getMethodParameterValue(programMethod, index); if (value != null && value.isParticular()) { constantParameterVisitor.visitProgramMethod(programClass, programMethod); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/DuplicateInitializerFixer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.info.*; import proguard.util.ProcessingFlags; /** * This MemberVisitor adds an additional parameter to the duplicate * initialization methods that it visits. */ public class DuplicateInitializerFixer implements MemberVisitor, AttributeVisitor { private static final Logger logger = LogManager.getLogger(DuplicateInitializerFixer.class); private static final char[] TYPES = new char[] { TypeConstants.BYTE, TypeConstants.CHAR, TypeConstants.SHORT, TypeConstants.INT, TypeConstants.BOOLEAN }; private final MemberVisitor extraFixedInitializerVisitor; /** * Creates a new DuplicateInitializerFixer. */ public DuplicateInitializerFixer() { this(null); } /** * Creates a new DuplicateInitializerFixer with an extra visitor. * @param extraFixedInitializerVisitor an optional extra visitor for all * initializers that have been fixed. */ public DuplicateInitializerFixer(MemberVisitor extraFixedInitializerVisitor) { this.extraFixedInitializerVisitor = extraFixedInitializerVisitor; } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Is it a class instance initializer? String name = programMethod.getName(programClass); if (name.equals(ClassConstants.METHOD_NAME_INIT)) { // Is there already another initializer with the same descriptor? String descriptor = programMethod.getDescriptor(programClass); Method similarMethod = programClass.findMethod(name, descriptor); if (!programMethod.equals(similarMethod)) { // Should this initializer be preserved? if (KeepMarker.isKept(programMethod) || // PGD-18: preserve this initializer if the other one has been modified. (similarMethod.getProcessingFlags() & ProcessingFlags.MODIFIED) != 0) { // Fix the other initializer. // We'll just proceed if it is being kept as well; // apparently the descriptor types didn't matter so much. programMethod = (ProgramMethod)similarMethod; } int index = descriptor.indexOf(TypeConstants.METHOD_ARGUMENTS_CLOSE); // Try to find a new, unique descriptor. int typeCounter = 0; while (true) { // Construct the new descriptor by inserting a new type // as an additional last argument. StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.substring(0, index)); for (int arrayDimension = 0; arrayDimension < typeCounter / TYPES.length; arrayDimension++) { newDescriptorBuffer.append(TypeConstants.ARRAY); } newDescriptorBuffer.append(TYPES[typeCounter % TYPES.length]); newDescriptorBuffer.append(descriptor.substring(index)); String newDescriptor = newDescriptorBuffer.toString(); // Is the new initializer descriptor unique? if (programClass.findMethod(name, newDescriptor) == null) { logger.debug("DuplicateInitializerFixer:"); logger.debug(" [{}.{}{}] ({}) -> [{}]", programClass.getName(), name, descriptor, ClassUtil.externalClassAccessFlags(programMethod.getAccessFlags()), newDescriptor); // Update the descriptor. programMethod.u2descriptorIndex = new ConstantPoolEditor(programClass).addUtf8Constant(newDescriptor); // Fix the local variable frame size, the method // signature, and the parameter annotations, if // necessary. programMethod.attributesAccept(programClass, this); // Update the optimization info. MethodOptimizationInfo methodOptimizationInfo = ProgramMethodOptimizationInfo.getMethodOptimizationInfo(programMethod); if (methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ProgramMethodOptimizationInfo programMethodOptimizationInfo = (ProgramMethodOptimizationInfo)methodOptimizationInfo; int parameterCount = ClassUtil.internalMethodParameterCount(newDescriptor, programMethod.getAccessFlags()); programMethodOptimizationInfo.insertParameter(parameterCount - 1, 1); int parameterSize = programMethodOptimizationInfo.getParameterSize(); programMethodOptimizationInfo.setParameterSize(parameterSize + 1); programMethodOptimizationInfo.setParameterUsed(parameterSize); } // Visit the initializer, if required. if (extraFixedInitializerVisitor != null) { extraFixedInitializerVisitor.visitProgramMethod(programClass, programMethod); } // We're done with this constructor. return; } typeCounter++; } } } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // The minimum variable size is determined by the arguments. int maxLocals = ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz), method.getAccessFlags()); if (codeAttribute.u2maxLocals < maxLocals) { codeAttribute.u2maxLocals = maxLocals; } } public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute signatureAttribute) { String descriptor = method.getDescriptor(clazz); int descriptorIndex = descriptor.indexOf(TypeConstants.METHOD_ARGUMENTS_CLOSE); String signature = signatureAttribute.getSignature(clazz); int signatureIndex = signature.indexOf(TypeConstants.METHOD_ARGUMENTS_CLOSE); String newSignature = signature.substring(0, signatureIndex) + descriptor.charAt(descriptorIndex - 1) + signature.substring(signatureIndex); // Update the signature. signatureAttribute.u2signatureIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); } public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) { // Update the number of parameters. int oldParametersCount = parameterAnnotationsAttribute.u1parametersCount++; if (parameterAnnotationsAttribute.u2parameterAnnotationsCount == null || parameterAnnotationsAttribute.u2parameterAnnotationsCount.length < parameterAnnotationsAttribute.u1parametersCount) { int[] annotationsCounts = new int[parameterAnnotationsAttribute.u1parametersCount]; Annotation[][] annotations = new Annotation[parameterAnnotationsAttribute.u1parametersCount][]; System.arraycopy(parameterAnnotationsAttribute.u2parameterAnnotationsCount, 0, annotationsCounts, 0, oldParametersCount); System.arraycopy(parameterAnnotationsAttribute.parameterAnnotations, 0, annotations, 0, oldParametersCount); parameterAnnotationsAttribute.u2parameterAnnotationsCount = annotationsCounts; parameterAnnotationsAttribute.parameterAnnotations = annotations; } } } ================================================ FILE: base/src/main/java/proguard/optimize/DuplicateInitializerInvocationFixer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; /** * This AttributeVisitor adds an additional integer parameter to the tweaked * initialization method invocations that it visits. */ public class DuplicateInitializerInvocationFixer implements AttributeVisitor, InstructionVisitor, ConstantVisitor, MemberVisitor { private static final Logger logger = LogManager.getLogger(DuplicateInitializerInvocationFixer.class); private final InstructionVisitor extraAddedInstructionVisitor; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); private String descriptor; private int descriptorLengthDelta; /** * Creates a new DuplicateInitializerInvocationFixer. */ public DuplicateInitializerInvocationFixer() { this(null); } /** * Creates a new DuplicateInitializerInvocationFixer. * @param extraAddedInstructionVisitor an optional extra visitor for all * added instructions. */ public DuplicateInitializerInvocationFixer(InstructionVisitor extraAddedInstructionVisitor) { this.extraAddedInstructionVisitor = extraAddedInstructionVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Reset the code changes. codeAttributeEditor.reset(codeAttribute.u4codeLength); // Fix any duplicate constructor invocations. codeAttribute.instructionsAccept(clazz, method, this); // Apply all accumulated changes to the code. codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_INVOKESPECIAL) { descriptorLengthDelta = 0; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); if (descriptorLengthDelta > 0) { Instruction extraInstruction = new SimpleInstruction(descriptorLengthDelta == 1 ? Instruction.OP_ICONST_0 : Instruction.OP_ACONST_NULL); codeAttributeEditor.insertBeforeInstruction(offset, extraInstruction); logger.debug(" [{}.{}{}] Inserting {} before {}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), extraInstruction.toString(), constantInstruction.toString(offset) ); if (extraAddedInstructionVisitor != null) { extraInstruction.accept(null, null, null, offset, extraAddedInstructionVisitor); } } } } // Implementations for ConstantVisitor. public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { // Check the referenced constructor descriptor. if (anyMethodrefConstant.getName(clazz).equals(ClassConstants.METHOD_NAME_INIT)) { descriptor = anyMethodrefConstant.getType(clazz); anyMethodrefConstant.referencedMethodAccept(this); } } // Implementations for MemberVisitor. public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { descriptorLengthDelta = programMethod.getDescriptor(programClass).length() - descriptor.length(); if (descriptorLengthDelta > 0) { logger.debug("DuplicateInitializerInvocationFixer:"); logger.debug(" [{}.{}{}] ({}) referenced by:", programClass.getName(), programMethod.getName(programClass), programMethod.getDescriptor(programClass), ClassUtil.externalClassAccessFlags(programMethod.getAccessFlags()) ); } } } ================================================ FILE: base/src/main/java/proguard/optimize/InfluenceFixpointVisitor.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.visitor.*; import java.util.*; import java.util.concurrent.*; /** * This ClassPoolVisitor visits members using visitors created by a factory. When any member X is changed, all other members * that refer or connect to X are revisited. This is repeated until fix point. * * This class is used for side effect marking where, once a side effect is found, all methods referring to it * could also have side effects. */ public class InfluenceFixpointVisitor implements ClassPoolVisitor { private static final Logger logger = LogManager.getFormatterLogger(InfluenceFixpointVisitor.class); // A copy of the code in ParallelAllClassVisitor. private static final int THREAD_COUNT; static { Integer threads = null; try { String threadCountString = System.getProperty("parallel.threads"); if (threadCountString != null) { threads = Integer.parseInt(threadCountString); } } catch (Exception ignored) {} threads = threads == null ? Runtime.getRuntime().availableProcessors() - 1 : Math.min(threads, Runtime.getRuntime().availableProcessors()); THREAD_COUNT = Math.max(1, threads); } private final MemberVisitorFactory memberVisitorFactory; private ReverseDependencyStore reverseDependencyStore; private final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT, new MyThreadFactory()); private final Set queuedAnalyses = Collections.synchronizedSet(new HashSet()); private final CountLatch countLatch = new CountLatch(); /** * Creates a mew InfluenceFixpointVisitor * @param memberVisitorFactory The factory of membervisitors that will be used to visit all the classes */ public InfluenceFixpointVisitor(MemberVisitorFactory memberVisitorFactory) { this.memberVisitorFactory = memberVisitorFactory; } // Implementations for ClassPoolVisitor. @Override public void visitClassPool(ClassPool classPool) { // This variable represents the ReverseDependencyStore to know which classes are impacted on change reverseDependencyStore = new ReverseDependencyCalculator(classPool).reverseDependencyStore(); long start = System.currentTimeMillis(); try { // Submit analyses for all class members. // We're doing this on the main thread, so we're // sure at least some analyses will be submitted. classPool.classesAccept(new AllMemberVisitor( new MyAnalysisSubmitter())); // Wait for all analyses to finish. countLatch.await(); // Clean up the executor. executorService.shutdown(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Parallel execution is taking too long", e); } long end = System.currentTimeMillis(); logger.debug("InfluenceFixpointVisitor........................ took: %6d ms", (end - start)); } // Utility classes. /** * A factory of MemberVisitor instances. */ public interface MemberVisitorFactory { /** * Creates a MemberVisitor instance that should visit all the members * that have changed with the given influencedMethodCollector. */ MemberVisitor createMemberVisitor(MemberVisitor influencedMethodCollector); } /** * This thread factory creates analysis threads. */ private class MyThreadFactory implements ThreadFactory { // Implementations for ThreadFactory. @Override public Thread newThread(Runnable runnable) { return new MyAnalysisThread(runnable); } } /** * This thread runs analyses. */ private class MyAnalysisThread extends Thread { // Create a member visitor that runnables can reuse. private final MemberVisitor memberVisitor = memberVisitorFactory.createMemberVisitor( reverseDependencyStore.new InfluencedMethodTraveller( new MyAnalysisSubmitter())); public MyAnalysisThread(Runnable runnable) { super(runnable); } } /** * This MemberVisitor schedules visited class members for analysis. */ private class MyAnalysisSubmitter implements MemberVisitor { // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) { // Create a new analysis task. MyAnalysis analysis = new MyAnalysis(clazz, member); // Is the analysis not queued yet? if (queuedAnalyses.add(analysis)) { // First make sure the executor waits for the analysis. countLatch.increment(); // Queue the analysis. executorService.submit(analysis); } } } /** * This Runnable analyzes a given class member. */ private class MyAnalysis implements Runnable { private final Clazz clazz; private final Member member; private MyAnalysis(Clazz clazz, Member member) { this.clazz = clazz; this.member = member; } // Implementations for Runnable. @Override public void run() { try { // Remove ourselves from the set of queued analyses. This is a // conservative approach: it's possible that the same analysis // is queued again right away. queuedAnalyses.remove(this); // Perform the actual analysis. // Reuse the thread's member visitor. MemberVisitor memberVisitor = ((MyAnalysisThread)Thread.currentThread()).memberVisitor; member.accept(clazz, memberVisitor); } finally { // Allow the executor to end if we're the last analysis. countLatch.decrement(); } } // Implementations for Object. @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyAnalysis that = (MyAnalysis)o; return Objects.equals(clazz, that.clazz) && Objects.equals(member, that.member); } @Override public int hashCode() { return Objects.hash(clazz, member); } } /** * This latch allows one or more threads to wait until other threads have * incremented and then decremented its internal counter to 0. Be careful * to increment it (if applicable) before waiting for it. */ private static class CountLatch { private int counter; /** * Increments the internal counter. */ public synchronized void increment() { counter++; } /** * Decrements the internal counter. */ public synchronized void decrement() { if (--counter <= 0) { // Wake up all threads that are waiting for the monitor. // They may proceed as soon as we relinquish the // synchronization lock. notifyAll(); } } /** * Waits for the internal counter to become 0, if it's larger than 0. */ public synchronized void await() throws InterruptedException { if (counter > 0) { // Relinquish the synchronization lock and wait for the // monitor. wait(); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/KeepMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.visitor.*; import proguard.optimize.info.*; /** * This ClassVisitor, MemberVisitor and * AttributeVisitor marks classes, class members and * code attributes it visits. The marked elements will remain * unchanged as necessary in the optimization step. * * @see NoSideEffectMethodMarker * @author Eric Lafortune */ public class KeepMarker implements ClassVisitor, MemberVisitor, AttributeVisitor { // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { ClassOptimizationInfo.setClassOptimizationInfo(clazz); } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { FieldOptimizationInfo.setFieldOptimizationInfo(programClass, programField); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { MethodOptimizationInfo.setMethodOptimizationInfo(programClass, programMethod); } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { FieldOptimizationInfo.setFieldOptimizationInfo(libraryClass, libraryField); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { MethodOptimizationInfo.setMethodOptimizationInfo(libraryClass, libraryMethod); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { CodeAttributeOptimizationInfo.setCodeAttributeOptimizationInfo(codeAttribute); } // Small utility methods. public static boolean isKept(Clazz clazz) { ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz); return info != null && info.isKept(); } public static boolean isKept(Field field) { FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field); return info != null && info.isKept(); } public static boolean isKept(Method method) { MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method); return info != null && info.isKept(); } public static boolean isKept(CodeAttribute codeAttribute) { CodeAttributeOptimizationInfo info = CodeAttributeOptimizationInfo.getCodeAttributeOptimizationInfo(codeAttribute); return info != null && info.isKept(); } } ================================================ FILE: base/src/main/java/proguard/optimize/KeptClassFilter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates its visits to one of two ClassVisitor's, * depending on whether the visited class is kept or not. * * @see KeepMarker * * @author Eric Lafortune */ public class KeptClassFilter implements ClassVisitor { private final ClassVisitor acceptedVisitor; private final ClassVisitor rejectedVisitor; /** * Creates a new KeptClassFilter. * * @param acceptedVisitor the class visitor to which accepted (kept) * classes will be delegated. */ public KeptClassFilter(ClassVisitor acceptedVisitor) { this(acceptedVisitor, null); } /** * Creates a new KeptClassFilter. * * @param acceptedVisitor the class visitor to which accepted (kept) * classes will be delegated. * @param rejectedVisitor the class visitor to which rejected (unkept) * classes will be delegated. */ public KeptClassFilter(ClassVisitor acceptedVisitor, ClassVisitor rejectedVisitor) { this.acceptedVisitor = acceptedVisitor; this.rejectedVisitor = rejectedVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { ClassVisitor delegateVisitor = selectVisitor(clazz); if (delegateVisitor != null) { clazz.accept(delegateVisitor); } } // Small utility methods. private ClassVisitor selectVisitor(Clazz clazz) { return KeepMarker.isKept(clazz) ? acceptedVisitor : rejectedVisitor; } } ================================================ FILE: base/src/main/java/proguard/optimize/KeptMemberFilter.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor delegates all its method calls to another MemberVisitor, * but only for Member objects that are marked as kept. * * @see KeepMarker * * @author Eric Lafortune */ public class KeptMemberFilter implements MemberVisitor { private final MemberVisitor memberVisitor; /** * Creates a new KeptMemberFilter. * @param memberVisitor the member visitor to which the visiting will be * delegated. */ public KeptMemberFilter(MemberVisitor memberVisitor) { this.memberVisitor = memberVisitor; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { if (KeepMarker.isKept(programField)) { memberVisitor.visitProgramField(programClass, programField); } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (KeepMarker.isKept(programMethod)) { memberVisitor.visitProgramMethod(programClass, programMethod); } } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { if (KeepMarker.isKept(libraryField)) { memberVisitor.visitLibraryField(libraryClass, libraryField); } } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { if (KeepMarker.isKept(libraryMethod)) { memberVisitor.visitLibraryMethod(libraryClass, libraryMethod); } } } ================================================ FILE: base/src/main/java/proguard/optimize/LineNumberTrimmer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.optimize; import proguard.AppView; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.editor.LineNumberTableAttributeTrimmer; import proguard.pass.Pass; /** * Trims the line number table attributes of all program classes. * * @author Tim Van Den Broecke */ public class LineNumberTrimmer implements Pass { @Override public void execute(AppView appView) { appView.programClassPool.classesAccept(new AllAttributeVisitor(true, new LineNumberTableAttributeTrimmer())); } } ================================================ FILE: base/src/main/java/proguard/optimize/MemberDescriptorSpecializer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.AccessConstants; import proguard.classfile.ClassConstants; import proguard.classfile.Clazz; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramField; import proguard.classfile.ProgramMember; import proguard.classfile.ProgramMethod; import proguard.classfile.TypeConstants; import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.editor.MemberReferenceFixer; import proguard.classfile.util.ClassUtil; import proguard.classfile.util.InternalTypeEnumeration; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.value.Value; import proguard.optimize.evaluation.StoringInvocationUnit; import proguard.util.ProcessingFlags; /** * This MemberVisitor specializes class types in the descriptors of the fields * and methods that it visits. * * Note that ClassReferenceFixer isn't necessary (and wouldn't work, because * of specialized array types), but MemberReferenceFixer is. * * @see StoringInvocationUnit * @see MemberReferenceFixer * @author Eric Lafortune */ public class MemberDescriptorSpecializer implements MemberVisitor { private static final Logger logger = LogManager.getLogger(MemberDescriptorSpecializer.class); private final boolean specializeFieldTypes; private final boolean specializeMethodParameterTypes; private final boolean specializeMethodReturnTypes; private final MemberVisitor extraTypeFieldVisitor; private final MemberVisitor extraParameterTypeMethodVisitor; private final MemberVisitor extraReturnTypeMethodVisitor; /** * Creates a new MemberDescriptorSpecializer. * @param specializeFieldTypes specifies whether field types * should be specialized. * @param specializeMethodParameterTypes specifies whether method parameter * types should be specialized. * @param specializeMethodReturnTypes specifies whether method return * types should be specialized. */ public MemberDescriptorSpecializer(boolean specializeFieldTypes, boolean specializeMethodParameterTypes, boolean specializeMethodReturnTypes) { this(specializeFieldTypes, specializeMethodParameterTypes, specializeMethodReturnTypes, null, null, null); } /** * Creates a new MemberDescriptorSpecializer with extra visitors. * @param specializeFieldTypes specifies whether field types * should be specialized. * @param specializeMethodParameterTypes specifies whether method parameter * types should be specialized. * @param specializeMethodReturnTypes specifies whether method return * types should be specialized. * @param extraTypeFieldVisitor an optional extra visitor for all * fields whose types have been * specialized. * @param extraParameterTypeMethodVisitor an optional extra visitor for all * methods whose parameter types have * been specialized. * @param extraReturnTypeMethodVisitor an optional extra visitor for all * methods whose return types have * been specialized. */ public MemberDescriptorSpecializer(boolean specializeFieldTypes, boolean specializeMethodParameterTypes, boolean specializeMethodReturnTypes, MemberVisitor extraTypeFieldVisitor, MemberVisitor extraParameterTypeMethodVisitor, MemberVisitor extraReturnTypeMethodVisitor) { this.specializeFieldTypes = specializeFieldTypes; this.specializeMethodParameterTypes = specializeMethodParameterTypes; this.specializeMethodReturnTypes = specializeMethodReturnTypes; this.extraTypeFieldVisitor = extraTypeFieldVisitor; this.extraParameterTypeMethodVisitor = extraParameterTypeMethodVisitor; this.extraReturnTypeMethodVisitor = extraReturnTypeMethodVisitor; } // Implementations for MemberVisitor. @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { String fieldType = programField.getDescriptor(programClass); if (specializeFieldTypes && ClassUtil.isInternalClassType(fieldType)) { // Is the value's type different from the declared type? Value value = StoringInvocationUnit.getFieldValue(programField); String valueType = valueType(fieldType, value); if (!valueType.equals(fieldType)) { // Is the value's class really more specific than the declared // type? Sometimes, it's an unrelated class or interface, which // can cause problems. Clazz valueClass = value.referenceValue().getReferencedClass(); if (valueClass != null && valueClass.extendsOrImplements(ClassUtil.internalClassNameFromClassType(fieldType)) && (programField.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { logger.debug("MemberDescriptorSpecializer [{}.{} {}] -> {}", programClass.getName(), programField.getName(programClass), programField.getDescriptor(programClass), valueType ); // Set the specialized referenced class. programField.referencedClass = value.referenceValue().getReferencedClass(); // Update the descriptor (and name). updateDescriptor(programClass, programField, valueType); // Visit the field, if required. if (extraTypeFieldVisitor != null) { extraTypeFieldVisitor.visitProgramField(programClass, programField); } } } } } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Only bother if there are referenced classes. Clazz[] referencedClasses = programMethod.referencedClasses; if (referencedClasses != null) { String descriptor = programMethod.getDescriptor(programClass); // All parameters of non-static methods are shifted by one in the local // variable frame. boolean isStatic = (programMethod.getAccessFlags() & AccessConstants.STATIC) != 0; int parameterIndex = isStatic ? 0 : 1; int classIndex = 0; // Go over the parameters. InternalTypeEnumeration parameterTypeEnumeration = new InternalTypeEnumeration(descriptor); StringBuilder newDescriptorBuffer = new StringBuilder(descriptor.length()); newDescriptorBuffer.append(TypeConstants.METHOD_ARGUMENTS_OPEN); while (parameterTypeEnumeration.hasMoreTypes()) { String parameterType = parameterTypeEnumeration.nextType(); if (specializeMethodParameterTypes && ClassUtil.isInternalClassType(parameterType)) { // Is the value's type different from the declared type? Value value = StoringInvocationUnit.getMethodParameterValue(programMethod, parameterIndex); String valueType = valueType(parameterType, value); if (!valueType.equals(parameterType)) { // Is the value's class really more specific than the // declared type? Sometimes, it's an unrelated class or // interface, which can cause problems. Clazz valueClass = value.referenceValue().getReferencedClass(); if (valueClass != null && valueClass.extendsOrImplements(ClassUtil.internalClassNameFromClassType(parameterType)) && (programMethod.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { logger.debug("MemberDescriptorSpecializer [{}.{}{}]: parameter #{}: {} -> {}", programClass.getName(), programMethod.getName(programClass), descriptor, parameterIndex, parameterType, valueType ); // Set the specialized type. referencedClasses[classIndex] = valueClass; // Visit the method, if required. if (extraParameterTypeMethodVisitor != null) { extraParameterTypeMethodVisitor.visitProgramMethod(programClass, programMethod); } parameterType = valueType; } } } // Compose the new descriptor. newDescriptorBuffer.append(parameterType); // Note that the specialized type may no longer be a class type, // for instance when specializing java.lang.Object to int[]. if (ClassUtil.isInternalClassType(parameterType)) { classIndex++; } parameterIndex++; } newDescriptorBuffer.append(TypeConstants.METHOD_ARGUMENTS_CLOSE); // Check the return type. String returnType = parameterTypeEnumeration.returnType(); if (specializeMethodReturnTypes && ClassUtil.isInternalClassType(returnType)) { // Is the value's type more specific than the declared type? Value value = StoringInvocationUnit.getMethodReturnValue(programMethod); String valueType = valueType(returnType, value); if (!valueType.equals(returnType)) { // Is the value's class really more specific than the // declared type? Sometimes, it's an unrelated class or // interface, which can cause problems. Clazz valueClass = value.referenceValue().getReferencedClass(); if (valueClass != null && valueClass.extendsOrImplements(ClassUtil.internalClassNameFromClassType(returnType)) && (valueClass.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { logger.debug("MemberDescriptorSpecializer [{}.{}{}]: return value: {} -> {}", programClass.getName(), programMethod.getName(programClass), descriptor, returnType, valueType ); // Set the specialized type. referencedClasses[classIndex] = value.referenceValue().getReferencedClass(); // Visit the method, if required. if (extraReturnTypeMethodVisitor != null) { extraReturnTypeMethodVisitor.visitProgramMethod(programClass, programMethod); } returnType = valueType; } } } // Compose the new descriptor. newDescriptorBuffer.append(returnType); // Is the new descriptor different from the old one? String newDescriptor = newDescriptorBuffer.toString(); if (!newDescriptor.equals(descriptor)) { // Update the descriptor (and name). updateDescriptor(programClass, programMethod, newDescriptor); } } } // Small utility methods. /** * Returns the specialized type, if possible, based on the given value. */ private String valueType(String type, Value value) { // The type always remains unchanged if it's not a reference value. if (value == null || value.computationalType() != Value.TYPE_REFERENCE) { return type; } // The type may be null if the reference value is always null. String valueType = value.referenceValue().getType(); if (valueType == null) { return type; } return valueType; } /** * Sets the given new descriptor on the given class member, and ensures that * it gets a unique name. */ private void updateDescriptor(ProgramClass programClass, ProgramMember programMember, String newDescriptor) { String descriptor = programMember.getDescriptor(programClass); ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass); // Update the descriptor. programMember.u2descriptorIndex = constantPoolEditor.addUtf8Constant(newDescriptor); // Update the name. String name = programMember.getName(programClass); String newName = newUniqueMemberName(name, descriptor); programMember.u2nameIndex = constantPoolEditor.addUtf8Constant(newName); } /** * Returns a unique class member name, based on the given name and descriptor. */ private String newUniqueMemberName(String name, String descriptor) { return name.equals(ClassConstants.METHOD_NAME_INIT) ? ClassConstants.METHOD_NAME_INIT : name + TypeConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode())); } } ================================================ FILE: base/src/main/java/proguard/optimize/MemberReferenceGeneralizer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.ClassVisitor; import proguard.util.ProcessingFlags; /** * This InstructionVisitor generalizes the classes referenced by the * field/method invocation instructions that it visits. * * For example, it replaces invocations * StringBuilder#toString() by Object#toString(), * Integer#intValue() by Number#intValue(), * Exception#printStackTrace() by Throwable#printStackTrace(). * * @author Eric Lafortune */ public class MemberReferenceGeneralizer implements InstructionVisitor, ConstantVisitor, ClassVisitor { private static final Logger logger = LogManager.getLogger(MemberReferenceGeneralizer.class); private final boolean fieldGeneralizationClass; private final boolean methodGeneralizationClass; private final CodeAttributeEditor codeAttributeEditor; private final InstructionVisitor extraFieldInstructionVisitor; private final InstructionVisitor extraMethodInstructionVisitor; // Fields acting as parameters and return values for the visitor methods. private int invocationOffset; private byte invocationOpcode; private String memberName; private String memberType; private Clazz generalizedClass; private Member generalizedMember; /** * Creates a new ReferenceGeneralizer. * @param fieldGeneralizationClass specifies whether field classes should * be generalized. * @param methodGeneralizationClass specifies whether method classes * should be generalized. * @param codeAttributeEditor a code editor that can be used to * accumulate changes to the code. */ public MemberReferenceGeneralizer(boolean fieldGeneralizationClass, boolean methodGeneralizationClass, CodeAttributeEditor codeAttributeEditor) { this(fieldGeneralizationClass, methodGeneralizationClass, codeAttributeEditor, null, null); } /** * Creates a new ReferenceGeneralizer. * @param fieldGeneralizationClass specifies whether field classes * should be generalized. * @param methodGeneralizationClass specifies whether method classes * should be generalized. * @param codeAttributeEditor a code editor that can be used to * accumulate changes to the code. * @param extraFieldInstructionVisitor an extra visitor for all field * access instructions whose * referenced classes have been * generalized. * @param extraMethodInstructionVisitor an extra visitor for all method * invocations instructions whose * referenced classes have been * generalized. */ public MemberReferenceGeneralizer(boolean fieldGeneralizationClass, boolean methodGeneralizationClass, CodeAttributeEditor codeAttributeEditor, InstructionVisitor extraFieldInstructionVisitor, InstructionVisitor extraMethodInstructionVisitor) { this.fieldGeneralizationClass = fieldGeneralizationClass; this.methodGeneralizationClass = methodGeneralizationClass; this.codeAttributeEditor = codeAttributeEditor; this.extraFieldInstructionVisitor = extraFieldInstructionVisitor; this.extraMethodInstructionVisitor = extraMethodInstructionVisitor; } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { // Is it a virtual method invocation? byte opcode = constantInstruction.opcode; switch (opcode) { case Instruction.OP_GETFIELD: case Instruction.OP_PUTFIELD: { if (fieldGeneralizationClass && !codeAttributeEditor.isModified(offset)) { // Try to generalize the field reference. invocationOffset = offset; invocationOpcode = opcode; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); // Did we find a class with the class member? if (generalizedClass != null && extraFieldInstructionVisitor != null) { extraFieldInstructionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); } } break; } case Instruction.OP_INVOKEVIRTUAL: { if (methodGeneralizationClass && !codeAttributeEditor.isModified(offset)) { // Try to generalize the method invocation. invocationOffset = offset; invocationOpcode = opcode; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); // Did we find a class with the class member? if (generalizedClass != null && extraMethodInstructionVisitor != null) { extraMethodInstructionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); } } break; } } } // Implementations for ConstantVisitor. @Override public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { // Try to find the class member in the super class (or higher). memberName = refConstant.getName(clazz); memberType = refConstant.getType(clazz); generalizedClass = null; generalizedMember = null; Member referencedMember = refConstant instanceof FieldrefConstant ? ((FieldrefConstant)refConstant).referencedField : ((AnyMethodrefConstant)refConstant).referencedMethod; // DGD-486: Only generalize members which are always available. Partial replacement of a class that is not // available on all platforms may result in a VerifyError at runtime. if (referencedMember != null && (referencedMember.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { clazz.constantPoolEntryAccept(refConstant.u2classIndex, this); // Did we find a class with the class member? if (generalizedClass != null && // only update the RefConstant if the generalized class is different // from the existing one. This can happen for fields as we need to first // look in the current class. Use the actual class name to compare as the // ClassReferenceInitializer might have found the proper class in the // hierarchy. !generalizedClass.getName().equals(refConstant.getClassName(clazz))) { logger.debug("ReferenceGeneralizer: [{}] invocation [{}.{}{}] -> [{}. ...]", clazz.getName(), refConstant.getClassName(clazz), memberName, memberType, generalizedClass.getName() ); int refConstantIndex; // Prevent concurrent modification of the constant pool from multiple threads. synchronized (clazz) { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); // Replace the invocation instruction. int generalizedClassConstantIndex = constantPoolEditor.addClassConstant(generalizedClass); refConstantIndex = invocationOpcode != Instruction.OP_INVOKEVIRTUAL ? constantPoolEditor.addFieldrefConstant(generalizedClassConstantIndex, memberName, memberType, generalizedClass, (Field)generalizedMember) : constantPoolEditor.addMethodrefConstant(generalizedClassConstantIndex, memberName, memberType, generalizedClass, (Method)generalizedMember); } ConstantInstruction replacementInstruction = new ConstantInstruction(invocationOpcode, refConstantIndex); codeAttributeEditor.replaceInstruction(invocationOffset, replacementInstruction); } else { generalizedClass = null; } } } @Override public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Try to find the method in the super class (or higher). classConstant.referencedClassAccept(this); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { if (invocationOpcode == Instruction.OP_INVOKEVIRTUAL) { // Do we have a super class? Clazz superClass = clazz.getSuperClass(); if (superClass != null) { // First look higher up in the hierarchy. superClass.accept(this); // Otherwise, look in the super class itself. // Only consider public classes and methods, to avoid any // access problems. if (generalizedClass == null && (superClass.getAccessFlags() & AccessConstants.PUBLIC) != 0) { Method method = superClass.findMethod(memberName, memberType); if (method != null && (method.getAccessFlags() & AccessConstants.PUBLIC) != 0 && (method.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { // Remember the generalized class and class member. generalizedClass = superClass; generalizedMember = method; } } } } else { // First look for the field in the class itself. Field field = clazz.findField(memberName, memberType); if (field != null && (field.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { // Remember the generalized class and class member. generalizedClass = clazz; generalizedMember = field; } else { // Do we have a super class? Clazz superClass = clazz.getSuperClass(); if (superClass != null) { // Look higher up in the hierarchy. superClass.accept(this); } } } } } ================================================ FILE: base/src/main/java/proguard/optimize/MethodDescriptorShrinker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.AccessConstants; import proguard.classfile.ClassConstants; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramMethod; import proguard.classfile.TypeConstants; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.SignatureAttribute; import proguard.classfile.attribute.annotation.Annotation; import proguard.classfile.attribute.annotation.ParameterAnnotationsAttribute; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.util.ClassUtil; import proguard.classfile.util.DescriptorClassEnumeration; import proguard.classfile.util.InternalTypeEnumeration; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.info.ParameterUsageMarker; import proguard.optimize.info.VariableUsageMarker; /** * This MemberVisitor removes unused parameters in the descriptors of the * methods that it visits. It also updates the signatures and parameter * annotations. * * @see ParameterUsageMarker * @see VariableUsageMarker * @see ParameterShrinker * @author Eric Lafortune */ public class MethodDescriptorShrinker implements MemberVisitor, AttributeVisitor { private static final Logger logger = LogManager.getLogger(MethodDescriptorShrinker.class); private final MemberVisitor extraMemberVisitor; /** * Creates a new MethodDescriptorShrinker. */ public MethodDescriptorShrinker() { this(null); } /** * Creates a new MethodDescriptorShrinker with an extra visitor. * @param extraMemberVisitor an optional extra visitor for all methods whose * parameters have been simplified. */ public MethodDescriptorShrinker(MemberVisitor extraMemberVisitor) { this.extraMemberVisitor = extraMemberVisitor; } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { logger.debug("MethodDescriptorShrinker: [{}.{}{}]", programClass.getName(), programMethod.getName(programClass), programMethod.getDescriptor(programClass) ); // Update the descriptor if it has any unused parameters. String descriptor = programMethod.getDescriptor(programClass); String newDescriptor = shrinkDescriptor(programMethod, descriptor, 0); if (!newDescriptor.equals(descriptor)) { // Shrink the signature and parameter annotations, // before shrinking the descriptor itself. programMethod.attributesAccept(programClass, this); String name = programMethod.getName(programClass); String newName = name; // Append a code, if the method isn't a class instance initializer. if (!name.equals(ClassConstants.METHOD_NAME_INIT)) { newName += TypeConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode())); } ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass); // Update the name, if necessary. if (!newName.equals(name)) { programMethod.u2nameIndex = constantPoolEditor.addUtf8Constant(newName); } // Update the referenced classes. programMethod.referencedClasses = shrinkReferencedClasses(programMethod, descriptor, 0, programMethod.referencedClasses); // Finally, update the descriptor itself. programMethod.u2descriptorIndex = constantPoolEditor.addUtf8Constant(newDescriptor); logger.debug(" -> [{}{}]", newName, newDescriptor); // Visit the method, if required. if (extraMemberVisitor != null) { extraMemberVisitor.visitProgramMethod(programClass, programMethod); } } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute signatureAttribute) { logger.debug(" [{}]", signatureAttribute.getSignature(clazz)); // Compute the new signature. String signature = signatureAttribute.getSignature(clazz); // Constructors of enum classes and of non-static inner classes may // start with one or two synthetic parameters, which are not part // of the signature. int syntheticParametersSize = new InternalTypeEnumeration(method.getDescriptor(clazz)).typesSize() - new InternalTypeEnumeration(signature).typesSize(); String newSignature = shrinkDescriptor(method, signature, syntheticParametersSize); if (!newSignature.equals(signature)) { // Update the signature. signatureAttribute.u2signatureIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); // Update the referenced classes. signatureAttribute.referencedClasses = shrinkReferencedClasses(method, signature, syntheticParametersSize, signatureAttribute.referencedClasses); logger.debug(" -> [{}]", newSignature); } } public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) { int[] annotationsCounts = parameterAnnotationsAttribute.u2parameterAnnotationsCount; Annotation[][] annotations = parameterAnnotationsAttribute.parameterAnnotations; String descriptor = method.getDescriptor(clazz); // Constructors of enum classes and of non-static inner classes may // start with one or two synthetic parameters, which are not part // of the signature and not counted for parameter annotations. int syntheticParameterCount = new InternalTypeEnumeration(descriptor).typeCount() - parameterAnnotationsAttribute.u1parametersCount; int syntheticParametersSize = ClassUtil.internalMethodVariableIndex(descriptor, true, syntheticParameterCount); // All parameters of non-static methods are shifted by one in the local // variable frame. int parameterIndex = syntheticParametersSize + ((method.getAccessFlags() & AccessConstants.STATIC) != 0 ? 0 : 1); int annotationIndex = 0; int newAnnotationIndex = 0; // Go over the parameters. InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); // Skip synthetic parameters that don't have annotations. for (int counter = 0; counter < syntheticParameterCount; counter++) { internalTypeEnumeration.nextType(); } // Shrink the annotations of the actual parameters. while (internalTypeEnumeration.hasMoreTypes()) { String type = internalTypeEnumeration.nextType(); if (ParameterUsageMarker.isParameterUsed(method, parameterIndex)) { annotationsCounts[newAnnotationIndex] = annotationsCounts[annotationIndex]; annotations[newAnnotationIndex++] = annotations[annotationIndex]; } annotationIndex++; parameterIndex += ClassUtil.internalTypeSize(type); } // Update the number of parameters. parameterAnnotationsAttribute.u1parametersCount = newAnnotationIndex; // Clear the unused entries. while (newAnnotationIndex < annotationIndex) { annotationsCounts[newAnnotationIndex] = 0; annotations[newAnnotationIndex++] = null; } } // Small utility methods. /** * Returns a shrunk descriptor or signature of the given method. */ private String shrinkDescriptor(Method method, String descriptor, int syntheticParametersSize) { // Signatures only start after any synthetic parameters. // All parameters of non-static methods are shifted by one in the local // variable frame. int parameterIndex = syntheticParametersSize + ((method.getAccessFlags() & AccessConstants.STATIC) != 0 ? 0 : 1); InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length()); // Copy the formal type parameters. newDescriptorBuffer.append(internalTypeEnumeration.formalTypeParameters()); newDescriptorBuffer.append(TypeConstants.METHOD_ARGUMENTS_OPEN); // Go over the parameters. while (internalTypeEnumeration.hasMoreTypes()) { String type = internalTypeEnumeration.nextType(); if (ParameterUsageMarker.isParameterUsed(method, parameterIndex)) { newDescriptorBuffer.append(type); } else logger.debug(" Deleting parameter #{} [{}]", parameterIndex, type); parameterIndex += ClassUtil.internalTypeSize(type); } // Copy the return type. newDescriptorBuffer.append(TypeConstants.METHOD_ARGUMENTS_CLOSE); newDescriptorBuffer.append(internalTypeEnumeration.returnType()); return newDescriptorBuffer.toString(); } /** * Shrinks the array of referenced classes of the given method. */ private Clazz[] shrinkReferencedClasses(Method method, String descriptor, int syntheticParametersSize, Clazz[] referencedClasses) { if (referencedClasses != null) { // Signatures only start after any synthetic parameters. // All parameters of non-static methods are shifted by one in the local // variable frame. int parameterIndex = syntheticParametersSize + ((method.getAccessFlags() & AccessConstants.STATIC) != 0 ? 0 : 1); InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); int referencedClassIndex = 0; int newReferencedClassIndex = 0; // Copy the formal type parameters. { String type = internalTypeEnumeration.formalTypeParameters(); int count = new DescriptorClassEnumeration(type).classCount(); for (int counter = 0; counter < count; counter++) { referencedClasses[newReferencedClassIndex++] = referencedClasses[referencedClassIndex++]; } } // Go over the parameters. while (internalTypeEnumeration.hasMoreTypes()) { // Consider the classes referenced by this parameter type. String type = internalTypeEnumeration.nextType(); int count = new DescriptorClassEnumeration(type).classCount(); if (ParameterUsageMarker.isParameterUsed(method, parameterIndex)) { // Copy the referenced classes. for (int counter = 0; counter < count; counter++) { referencedClasses[newReferencedClassIndex++] = referencedClasses[referencedClassIndex++]; } } else { // Skip the referenced classes. referencedClassIndex += count; } parameterIndex += ClassUtil.internalTypeSize(type); } // Copy the return type. { String type = internalTypeEnumeration.returnType(); int count = new DescriptorClassEnumeration(type).classCount(); for (int counter = 0; counter < count; counter++) { referencedClasses[newReferencedClassIndex++] = referencedClasses[referencedClassIndex++]; } } // Shrink the array to the proper size. if (newReferencedClassIndex == 0) { referencedClasses = null; } else if (newReferencedClassIndex < referencedClassIndex) { Clazz[] newReferencedClasses = new Clazz[newReferencedClassIndex]; System.arraycopy(referencedClasses, 0, newReferencedClasses, 0, newReferencedClassIndex); referencedClasses = newReferencedClasses; } } return referencedClasses; } } ================================================ FILE: base/src/main/java/proguard/optimize/MethodStaticizer.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.editor.MethodInvocationFixer; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.info.ParameterUsageMarker; import proguard.optimize.peephole.VariableShrinker; /** * This MemberVisitor makes all methods that it visits static, if their 'this' * parameters are unused. * * @see ParameterUsageMarker * @see MethodInvocationFixer * @see VariableShrinker * @author Eric Lafortune */ public class MethodStaticizer implements MemberVisitor { private final MemberVisitor extraStaticMemberVisitor; /** * Creates a new MethodStaticizer. */ public MethodStaticizer() { this(null); } /** * Creates a new MethodStaticizer with an extra visitor. * @param extraStaticMemberVisitor an optional extra visitor for all * methods that have been made static. */ public MethodStaticizer(MemberVisitor extraStaticMemberVisitor) { this.extraStaticMemberVisitor = extraStaticMemberVisitor; } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Is the 'this' parameter being used? if (!ParameterUsageMarker.isParameterUsed(programMethod, 0)) { // Make the method static. programMethod.u2accessFlags = (programMethod.getAccessFlags() & ~AccessConstants.FINAL) | AccessConstants.STATIC; // Visit the method, if required. if (extraStaticMemberVisitor != null) { extraStaticMemberVisitor.visitProgramMethod(programClass, programMethod); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/OptimizationInfoClassFilter.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; import proguard.optimize.info.*; /** * This ClassVisitor delegates its visits to another given * ClassVisitor, but only when the visited class has editable * optimization info. * * @see ClassOptimizationInfo * @see ProgramClassOptimizationInfo * @author Eric Lafortune */ public class OptimizationInfoClassFilter implements ClassVisitor { private final ClassVisitor classVisitor; /** * Creates a new OptimizationInfoClassFilter. * @param classVisitor the ClassVisitor to which visits will * be delegated. */ public OptimizationInfoClassFilter(ClassVisitor classVisitor) { this.classVisitor = classVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { // Does the field have optimization info? if (ClassOptimizationInfo.getClassOptimizationInfo(programClass) instanceof ProgramClassOptimizationInfo) { classVisitor.visitProgramClass(programClass); } } @Override public void visitLibraryClass(LibraryClass libraryClass) { // Does the class have optimization info? if (ClassOptimizationInfo.getClassOptimizationInfo(libraryClass) instanceof ProgramClassOptimizationInfo) { classVisitor.visitLibraryClass(libraryClass); } } } ================================================ FILE: base/src/main/java/proguard/optimize/OptimizationInfoMemberFilter.java ================================================ /* * 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; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.info.*; /** * This MemberVisitor delegates its visits to another given * MemberVisitor, but only when the visited member has editable * optimization info. * * @see FieldOptimizationInfo * @see ProgramFieldOptimizationInfo * @see MethodOptimizationInfo * @see ProgramMethodOptimizationInfo * @author Eric Lafortune */ public class OptimizationInfoMemberFilter implements MemberVisitor { private final MemberVisitor memberVisitor; private final MemberVisitor otherMemberVisitor; /** * Creates a new OptimizationInfoMemberFilter. * @param memberVisitor the MemberVisitor to which visits will * be delegated. */ public OptimizationInfoMemberFilter(MemberVisitor memberVisitor) { this(memberVisitor, null); } /** * Creates a new OptimizationInfoMemberFilter. * @param memberVisitor the MemberVisitor to which visits will * be delegated if the member has editable optimization * info. * @param otherMemberVisitor the MemberVisitor to which visits will * be delegated if the member does not have editable * optimization info. */ public OptimizationInfoMemberFilter(MemberVisitor memberVisitor, MemberVisitor otherMemberVisitor) { this.memberVisitor = memberVisitor; this.otherMemberVisitor = otherMemberVisitor; } // Implementations for MemberVisitor. public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {} public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {} public void visitProgramField(ProgramClass programClass, ProgramField programField) { MemberVisitor visitor = FieldOptimizationInfo.getFieldOptimizationInfo(programField) instanceof ProgramFieldOptimizationInfo ? memberVisitor : otherMemberVisitor; if (visitor != null) { visitor.visitProgramField(programClass, programField); } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { MemberVisitor visitor = MethodOptimizationInfo.getMethodOptimizationInfo(programMethod) instanceof ProgramMethodOptimizationInfo ? memberVisitor : otherMemberVisitor; if (visitor != null) { visitor.visitProgramMethod(programClass, programMethod); } } } ================================================ FILE: base/src/main/java/proguard/optimize/Optimizer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.ClassSpecificationVisitorFactory; import proguard.Configuration; import proguard.classfile.AccessConstants; import proguard.classfile.ClassPool; import proguard.classfile.VersionConstants; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AllBootstrapMethodInfoVisitor; import proguard.classfile.attribute.visitor.AllExceptionInfoVisitor; import proguard.classfile.attribute.visitor.AllInnerClassesInfoVisitor; import proguard.classfile.attribute.visitor.AttributeNameFilter; import proguard.classfile.attribute.visitor.AttributeProcessingFlagFilter; import proguard.classfile.attribute.visitor.CodeAttributeToMethodVisitor; import proguard.classfile.attribute.visitor.DebugAttributeVisitor; import proguard.classfile.attribute.visitor.MultiAttributeVisitor; import proguard.classfile.attribute.visitor.StackSizeComputer; import proguard.classfile.constant.Constant; import proguard.classfile.constant.visitor.AllBootstrapMethodArgumentVisitor; import proguard.classfile.constant.visitor.AllConstantVisitor; import proguard.classfile.constant.visitor.BootstrapMethodHandleTraveler; import proguard.classfile.constant.visitor.ConstantTagFilter; import proguard.classfile.constant.visitor.MethodrefTraveler; import proguard.classfile.editor.AccessFixer; import proguard.classfile.editor.ClassReferenceFixer; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.editor.ConstantPoolShrinker; import proguard.classfile.editor.InnerClassesAccessFixer; import proguard.classfile.editor.InstructionSequencesReplacer; import proguard.classfile.editor.MemberReferenceFixer; import proguard.classfile.editor.MethodInvocationFixer; import proguard.classfile.editor.PeepholeEditor; import proguard.classfile.editor.StackSizeUpdater; import proguard.classfile.instruction.visitor.AllInstructionVisitor; import proguard.classfile.instruction.visitor.InstructionConstantVisitor; import proguard.classfile.instruction.visitor.InstructionCounter; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.instruction.visitor.MultiInstructionVisitor; import proguard.classfile.kotlin.visitor.AllFunctionVisitor; import proguard.classfile.kotlin.visitor.AllPropertyVisitor; import proguard.classfile.kotlin.visitor.MultiKotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor; import proguard.classfile.util.BranchTargetFinder; import proguard.classfile.util.MethodLinker; import proguard.classfile.visitor.AllClassVisitor; import proguard.classfile.visitor.AllFieldVisitor; import proguard.classfile.visitor.AllMemberVisitor; import proguard.classfile.visitor.AllMethodVisitor; import proguard.classfile.visitor.BottomClassFilter; import proguard.classfile.visitor.ClassAccessFilter; import proguard.classfile.visitor.ClassCleaner; import proguard.classfile.visitor.ClassCounter; import proguard.classfile.visitor.ClassHierarchyTraveler; import proguard.classfile.visitor.ClassPoolVisitor; import proguard.classfile.visitor.ClassProcessingFlagFilter; import proguard.classfile.visitor.ClassVersionFilter; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.DotClassClassVisitor; import proguard.classfile.visitor.DynamicReturnedClassVisitor; import proguard.classfile.visitor.ExceptionCounter; import proguard.classfile.visitor.ExceptionHandlerConstantVisitor; import proguard.classfile.visitor.FunctionalInterfaceFilter; import proguard.classfile.visitor.InjectedClassFilter; import proguard.classfile.visitor.MemberAccessFilter; import proguard.classfile.visitor.MemberAccessFlagCleaner; import proguard.classfile.visitor.MemberCounter; import proguard.classfile.visitor.MemberDescriptorReferencedClassVisitor; import proguard.classfile.visitor.MemberProcessingFlagFilter; import proguard.classfile.visitor.MultiClassVisitor; import proguard.classfile.visitor.MultiConstantVisitor; import proguard.classfile.visitor.MultiMemberVisitor; import proguard.classfile.visitor.ParallelAllClassVisitor; import proguard.classfile.visitor.ReferencedClassVisitor; import proguard.classfile.visitor.ReferencedMemberVisitor; import proguard.evaluation.AssumeClassSpecificationVisitorFactory; import proguard.evaluation.InvocationUnit; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.ReferenceTracingValueFactory; import proguard.evaluation.SimplifiedInvocationUnit; import proguard.evaluation.value.BasicRangeValueFactory; import proguard.evaluation.value.DetailedArrayValueFactory; import proguard.evaluation.value.IdentifiedValueFactory; import proguard.evaluation.value.ParticularValueFactory; import proguard.evaluation.value.ValueFactory; import proguard.io.ExtraDataEntryNameMap; import proguard.optimize.evaluation.EvaluationShrinker; import proguard.optimize.evaluation.EvaluationSimplifier; import proguard.optimize.evaluation.InstructionUsageMarker; import proguard.optimize.evaluation.InstructionUsageMarker; import proguard.optimize.evaluation.LoadingInvocationUnit; import proguard.optimize.evaluation.ParameterTracingInvocationUnit; import proguard.optimize.evaluation.SimpleEnumArrayPropagator; import proguard.optimize.evaluation.SimpleEnumClassChecker; import proguard.optimize.evaluation.SimpleEnumClassSimplifier; import proguard.optimize.evaluation.SimpleEnumDescriptorSimplifier; import proguard.optimize.evaluation.SimpleEnumUseChecker; import proguard.optimize.evaluation.SimpleEnumUseSimplifier; import proguard.optimize.evaluation.StoringInvocationUnit; import proguard.optimize.evaluation.VariableOptimizer; import proguard.optimize.info.AccessMethodMarker; import proguard.optimize.info.BackwardBranchMarker; import proguard.optimize.info.CatchExceptionMarker; import proguard.optimize.info.CaughtClassMarker; import proguard.optimize.info.ContainsConstructorsMarker; import proguard.optimize.info.DotClassMarker; import proguard.optimize.info.DynamicInvocationMarker; import proguard.optimize.info.EscapingClassFilter; import proguard.optimize.info.EscapingClassMarker; import proguard.optimize.info.FinalFieldAssignmentMarker; import proguard.optimize.info.InstanceofClassMarker; import proguard.optimize.info.InstantiationClassMarker; import proguard.optimize.info.MethodInvocationMarker; import proguard.optimize.info.MutableBoolean; import proguard.optimize.info.NoEscapingParametersMethodMarker; import proguard.optimize.info.NoExternalReturnValuesMethodMarker; import proguard.optimize.info.NoExternalSideEffectMethodMarker; import proguard.optimize.info.NoSideEffectClassMarker; import proguard.optimize.info.NoSideEffectMethodMarker; import proguard.optimize.info.NonEmptyStackReturnMarker; import proguard.optimize.info.NonPrivateMemberMarker; import proguard.optimize.info.OptimizationCodeAttributeFilter; import proguard.optimize.info.PackageVisibleMemberContainingClassMarker; import proguard.optimize.info.PackageVisibleMemberInvokingClassMarker; import proguard.optimize.info.ParameterEscapeMarker; import proguard.optimize.info.ParameterUsageMarker; import proguard.optimize.info.ProgramClassOptimizationInfoSetter; import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; import proguard.optimize.info.ReadWriteFieldMarker; import proguard.optimize.info.SideEffectMethodMarker; import proguard.optimize.info.SimpleEnumFilter; import proguard.optimize.info.SimpleEnumMarker; import proguard.optimize.info.SuperInvocationMarker; import proguard.optimize.info.SynchronizedBlockMethodMarker; import proguard.optimize.info.UnusedParameterMethodFilter; import proguard.optimize.info.UnusedParameterOptimizationInfoUpdater; import proguard.optimize.info.WrapperClassMarker; import proguard.optimize.kotlin.KotlinContextReceiverUsageMarker; import proguard.optimize.peephole.ClassFinalizer; import proguard.optimize.peephole.GotoCommonCodeReplacer; import proguard.optimize.peephole.GotoGotoReplacer; import proguard.optimize.peephole.GotoReturnReplacer; import proguard.optimize.peephole.HorizontalClassMerger; import proguard.optimize.peephole.InstructionSequenceConstants; import proguard.optimize.peephole.MemberPrivatizer; import proguard.optimize.peephole.MethodFinalizer; import proguard.optimize.peephole.NoConstructorReferenceReplacer; import proguard.optimize.peephole.RetargetedClassFilter; import proguard.optimize.peephole.RetargetedInnerClassAttributeRemover; import proguard.optimize.peephole.ShortMethodInliner; import proguard.optimize.peephole.SingleInvocationMethodInliner; import proguard.optimize.peephole.TargetClassChanger; import proguard.optimize.peephole.UnreachableCodeRemover; import proguard.optimize.peephole.UnreachableExceptionRemover; import proguard.optimize.peephole.VariableShrinker; import proguard.optimize.peephole.VerticalClassMerger; import proguard.optimize.peephole.WrapperClassMerger; import proguard.optimize.peephole.WrapperClassUseSimplifier; import proguard.pass.Pass; import proguard.util.ConstantMatcher; import proguard.util.ListParser; import proguard.util.NameParser; import proguard.util.ProcessingFlagSetter; import proguard.util.ProcessingFlags; import proguard.util.StringMatcher; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * This pass optimizes class pools according to a given configuration. *

* This pass is stateful. It tracks when no more optimizations are * possible, and then all further runs of this pass will have no effect. * * @author Eric Lafortune */ public class Optimizer implements Pass { private static final Logger logger = LogManager.getLogger(Optimizer.class); public static final String LIBRARY_GSON = "library/gson"; private static final String CLASS_MARKING_FINAL = "class/marking/final"; private static final String CLASS_UNBOXING_ENUM = "class/unboxing/enum"; private static final String CLASS_MERGING_VERTICAL = "class/merging/vertical"; private static final String CLASS_MERGING_HORIZONTAL = "class/merging/horizontal"; private static final String CLASS_MERGING_WRAPPER = "class/merging/wrapper"; private static final String FIELD_REMOVAL_WRITEONLY = "field/removal/writeonly"; private static final String FIELD_MARKING_PRIVATE = "field/marking/private"; private static final String FIELD_GENERALIZATION_CLASS = "field/generalization/class"; private static final String FIELD_SPECIALIZATION_TYPE = "field/specialization/type"; private static final String FIELD_PROPAGATION_VALUE = "field/propagation/value"; private static final String METHOD_MARKING_PRIVATE = "method/marking/private"; private static final String METHOD_MARKING_STATIC = "method/marking/static"; private static final String METHOD_MARKING_FINAL = "method/marking/final"; private static final String METHOD_MARKING_SYNCHRONIZED = "method/marking/synchronized"; private static final String METHOD_REMOVAL_PARAMETER = "method/removal/parameter"; private static final String METHOD_GENERALIZATION_CLASS = "method/generalization/class"; private static final String METHOD_SPECIALIZATION_PARAMETER_TYPE = "method/specialization/parametertype"; private static final String METHOD_SPECIALIZATION_RETURN_TYPE = "method/specialization/returntype"; private static final String METHOD_PROPAGATION_PARAMETER = "method/propagation/parameter"; private static final String METHOD_PROPAGATION_RETURNVALUE = "method/propagation/returnvalue"; private static final String METHOD_INLINING_SHORT = "method/inlining/short"; private static final String METHOD_INLINING_UNIQUE = "method/inlining/unique"; private static final String METHOD_INLINING_TAILRECURSION = "method/inlining/tailrecursion"; private static final String CODE_MERGING = "code/merging"; private static final String CODE_SIMPLIFICATION_VARIABLE = "code/simplification/variable"; private static final String CODE_SIMPLIFICATION_ARITHMETIC = "code/simplification/arithmetic"; private static final String CODE_SIMPLIFICATION_CAST = "code/simplification/cast"; private static final String CODE_SIMPLIFICATION_FIELD = "code/simplification/field"; private static final String CODE_SIMPLIFICATION_BRANCH = "code/simplification/branch"; private static final String CODE_SIMPLIFICATION_OBJECT = "code/simplification/object"; private static final String CODE_SIMPLIFICATION_STRING = "code/simplification/string"; private static final String CODE_SIMPLIFICATION_MATH = "code/simplification/math"; private static final String CODE_SIMPLIFICATION_ADVANCED = "code/simplification/advanced"; private static final String CODE_REMOVAL_ADVANCED = "code/removal/advanced"; private static final String CODE_REMOVAL_SIMPLE = "code/removal/simple"; private static final String CODE_REMOVAL_VARIABLE = "code/removal/variable"; private static final String CODE_REMOVAL_EXCEPTION = "code/removal/exception"; private static final String CODE_ALLOCATION_VARIABLE = "code/allocation/variable"; public static final String[] OPTIMIZATION_NAMES = new String[] { LIBRARY_GSON, CLASS_MARKING_FINAL, CLASS_MERGING_VERTICAL, CLASS_MERGING_HORIZONTAL, FIELD_REMOVAL_WRITEONLY, FIELD_MARKING_PRIVATE, FIELD_GENERALIZATION_CLASS, FIELD_SPECIALIZATION_TYPE, FIELD_PROPAGATION_VALUE, METHOD_MARKING_PRIVATE, METHOD_MARKING_STATIC, METHOD_MARKING_FINAL, METHOD_MARKING_SYNCHRONIZED, METHOD_REMOVAL_PARAMETER, METHOD_GENERALIZATION_CLASS, METHOD_SPECIALIZATION_PARAMETER_TYPE, METHOD_SPECIALIZATION_RETURN_TYPE, METHOD_PROPAGATION_PARAMETER, METHOD_PROPAGATION_RETURNVALUE, METHOD_INLINING_SHORT, METHOD_INLINING_UNIQUE, METHOD_INLINING_TAILRECURSION, CODE_MERGING, CODE_SIMPLIFICATION_VARIABLE, CODE_SIMPLIFICATION_ARITHMETIC, CODE_SIMPLIFICATION_CAST, CODE_SIMPLIFICATION_FIELD, CODE_SIMPLIFICATION_BRANCH, CODE_SIMPLIFICATION_STRING, CODE_SIMPLIFICATION_MATH, CODE_SIMPLIFICATION_ADVANCED, CODE_REMOVAL_ADVANCED, CODE_REMOVAL_SIMPLE, CODE_REMOVAL_VARIABLE, CODE_REMOVAL_EXCEPTION, CODE_ALLOCATION_VARIABLE, }; private boolean libraryGson; private boolean classMarkingFinal; private boolean classUnboxingEnum; private boolean classMergingVertical; private boolean classMergingHorizontal; private boolean classMergingWrapper; private boolean fieldRemovalWriteonly; private boolean fieldMarkingPrivate; private boolean fieldGeneralizationClass; private boolean fieldSpecializationType; private boolean fieldPropagationValue; private boolean methodMarkingPrivate; private boolean methodMarkingStatic; private boolean methodMarkingFinal; private boolean methodMarkingSynchronized; private boolean methodRemovalParameter; private boolean methodGeneralizationClass; private boolean methodSpecializationParametertype; private boolean methodSpecializationReturntype; private boolean methodPropagationParameter; private boolean methodPropagationReturnvalue; private boolean methodInliningShort; private boolean methodInliningUnique; private boolean methodInliningTailrecursion; private boolean codeMerging; private boolean codeSimplificationVariable; private boolean codeSimplificationArithmetic; private boolean codeSimplificationCast; private boolean codeSimplificationField; private boolean codeSimplificationBranch; private boolean codeSimplificationObject; private boolean codeSimplificationString; private boolean codeSimplificationMath; private boolean codeSimplificationPeephole; private boolean codeSimplificationAdvanced; private boolean codeRemovalAdvanced; private boolean codeRemovalSimple; private boolean codeRemovalVariable; private boolean codeRemovalException; private boolean codeAllocationVariable; // The optimizer uses this field to communicate to its following // invocations that no further optimizations are possible. private boolean moreOptimizationsPossible = true; private int passIndex = 0; private final Configuration configuration; public Optimizer(Configuration configuration) { this.configuration = configuration; } @Override public void execute(AppView appView) throws IOException { if (!moreOptimizationsPossible) { return; } // Create a matcher for filtering optimizations. StringMatcher filter = configuration.optimizations != null ? new ListParser(new NameParser()).parse(configuration.optimizations) : new ConstantMatcher(true); libraryGson = filter.matches(LIBRARY_GSON); classMarkingFinal = filter.matches(CLASS_MARKING_FINAL); classUnboxingEnum = filter.matches(CLASS_UNBOXING_ENUM); classMergingVertical = !configuration.optimizeConservatively && filter.matches(CLASS_MERGING_VERTICAL); classMergingHorizontal = !configuration.optimizeConservatively && filter.matches(CLASS_MERGING_HORIZONTAL); classMergingWrapper = !configuration.optimizeConservatively && filter.matches(CLASS_MERGING_WRAPPER); fieldRemovalWriteonly = filter.matches(FIELD_REMOVAL_WRITEONLY); fieldMarkingPrivate = filter.matches(FIELD_MARKING_PRIVATE); fieldGeneralizationClass = filter.matches(FIELD_GENERALIZATION_CLASS); fieldSpecializationType = filter.matches(FIELD_SPECIALIZATION_TYPE); fieldPropagationValue = filter.matches(FIELD_PROPAGATION_VALUE); methodMarkingPrivate = filter.matches(METHOD_MARKING_PRIVATE); methodMarkingStatic = filter.matches(METHOD_MARKING_STATIC); methodMarkingFinal = filter.matches(METHOD_MARKING_FINAL); methodMarkingSynchronized = filter.matches(METHOD_MARKING_SYNCHRONIZED); methodRemovalParameter = filter.matches(METHOD_REMOVAL_PARAMETER); methodGeneralizationClass = filter.matches(METHOD_GENERALIZATION_CLASS); methodSpecializationParametertype = filter.matches(METHOD_SPECIALIZATION_PARAMETER_TYPE); methodSpecializationReturntype = filter.matches(METHOD_SPECIALIZATION_RETURN_TYPE); methodPropagationParameter = filter.matches(METHOD_PROPAGATION_PARAMETER); methodPropagationReturnvalue = filter.matches(METHOD_PROPAGATION_RETURNVALUE); methodInliningShort = filter.matches(METHOD_INLINING_SHORT); methodInliningUnique = filter.matches(METHOD_INLINING_UNIQUE); methodInliningTailrecursion = filter.matches(METHOD_INLINING_TAILRECURSION); codeMerging = filter.matches(CODE_MERGING); codeSimplificationVariable = filter.matches(CODE_SIMPLIFICATION_VARIABLE); codeSimplificationArithmetic = filter.matches(CODE_SIMPLIFICATION_ARITHMETIC); codeSimplificationCast = filter.matches(CODE_SIMPLIFICATION_CAST); codeSimplificationField = filter.matches(CODE_SIMPLIFICATION_FIELD); codeSimplificationBranch = filter.matches(CODE_SIMPLIFICATION_BRANCH); codeSimplificationObject = filter.matches(CODE_SIMPLIFICATION_OBJECT); codeSimplificationString = filter.matches(CODE_SIMPLIFICATION_STRING); codeSimplificationMath = filter.matches(CODE_SIMPLIFICATION_MATH); codeSimplificationAdvanced = filter.matches(CODE_SIMPLIFICATION_ADVANCED); codeRemovalAdvanced = filter.matches(CODE_REMOVAL_ADVANCED); codeRemovalSimple = filter.matches(CODE_REMOVAL_SIMPLE); codeRemovalVariable = filter.matches(CODE_REMOVAL_VARIABLE); codeRemovalException = filter.matches(CODE_REMOVAL_EXCEPTION); codeAllocationVariable = filter.matches(CODE_ALLOCATION_VARIABLE); // Some optimizations are required by other optimizations. codeSimplificationAdvanced = codeSimplificationAdvanced || fieldPropagationValue || methodPropagationParameter || methodPropagationReturnvalue; codeRemovalAdvanced = codeRemovalAdvanced || fieldRemovalWriteonly || methodMarkingStatic || methodRemovalParameter; codeRemovalSimple = codeRemovalSimple || codeSimplificationBranch; codeRemovalException = codeRemovalException || codeRemovalAdvanced || codeRemovalSimple; codeSimplificationPeephole = codeSimplificationVariable || codeSimplificationArithmetic || codeSimplificationCast || codeSimplificationField || codeSimplificationBranch || codeSimplificationObject || codeSimplificationString || codeSimplificationMath || fieldGeneralizationClass || methodGeneralizationClass; logger.info("Optimizing (pass {}/{})...", passIndex + 1, configuration.optimizationPasses); optimize(configuration, appView.programClassPool, appView.libraryClassPool, appView.extraDataEntryNameMap); passIndex++; } /** * Performs optimization of the given program class pool. */ private void optimize(Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool, ExtraDataEntryNameMap extraDataEntryNameMap) throws IOException { // Check if we have at least some keep commands. if (configuration.keep == null && configuration.applyMapping == null && configuration.printMapping == null) { throw new IOException("You have to specify '-keep' options for the optimization step."); } // Create counters to count the numbers of optimizations. final ClassCounter classMarkingFinalCounter = new ClassCounter(); final ClassCounter classUnboxingEnumCounter = new ClassCounter(); final ClassCounter classMergingVerticalCounter = new ClassCounter(); final ClassCounter classMergingHorizontalCounter = new ClassCounter(); final ClassCounter classMergingWrapperCounter = new ClassCounter(); final MemberCounter fieldRemovalWriteonlyCounter = new MemberCounter(); final MemberCounter fieldMarkingPrivateCounter = new MemberCounter(); final InstructionCounter fieldGeneralizationClassCounter = new InstructionCounter(); final MemberCounter fieldSpecializationTypeCounter = new MemberCounter(); final MemberCounter fieldPropagationValueCounter = new MemberCounter(); final MemberCounter methodMarkingPrivateCounter = new MemberCounter(); final MemberCounter methodMarkingStaticCounter = new MemberCounter(); final MemberCounter methodMarkingFinalCounter = new MemberCounter(); final MemberCounter methodMarkingSynchronizedCounter = new MemberCounter(); final MemberCounter methodRemovalParameterCounter1 = new MemberCounter(); final MemberCounter methodRemovalParameterCounter2 = new MemberCounter(); final InstructionCounter methodGeneralizationClassCounter = new InstructionCounter(); final MemberCounter methodSpecializationParametertypeCounter = new MemberCounter(); final MemberCounter methodSpecializationReturntypeCounter = new MemberCounter(); final MemberCounter methodPropagationParameterCounter = new MemberCounter(); final MemberCounter methodPropagationReturnvalueCounter = new MemberCounter(); final InstructionCounter methodInliningShortCounter = new InstructionCounter(); final InstructionCounter methodInliningUniqueCounter = new InstructionCounter(); final InstructionCounter methodInliningTailrecursionCounter = new InstructionCounter(); final InstructionCounter codeMergingCounter = new InstructionCounter(); final InstructionCounter codeSimplificationVariableCounter = new InstructionCounter(); final InstructionCounter codeSimplificationArithmeticCounter = new InstructionCounter(); final InstructionCounter codeSimplificationCastCounter = new InstructionCounter(); final InstructionCounter codeSimplificationFieldCounter = new InstructionCounter(); final InstructionCounter codeSimplificationBranchCounter = new InstructionCounter(); final InstructionCounter codeSimplificationObjectCounter = new InstructionCounter(); final InstructionCounter codeSimplificationStringCounter = new InstructionCounter(); final InstructionCounter codeSimplificationMathCounter = new InstructionCounter(); final InstructionCounter codeSimplificationAndroidMathCounter = new InstructionCounter(); final InstructionCounter codeSimplificationAdvancedCounter = new InstructionCounter(); final InstructionCounter deletedCounter = new InstructionCounter(); final InstructionCounter addedCounter = new InstructionCounter(); final MemberCounter codeRemovalVariableCounter = new MemberCounter(); final ExceptionCounter codeRemovalExceptionCounter = new ExceptionCounter(); final MemberCounter codeAllocationVariableCounter = new MemberCounter(); final MemberCounter initializerFixCounter1 = new MemberCounter(); final MemberCounter initializerFixCounter2 = new MemberCounter(); // Clean up any old processing info. programClassPool.classesAccept(new ClassCleaner()); libraryClassPool.classesAccept(new ClassCleaner()); // Link all methods that should get the same optimization info. programClassPool.classesAccept(new BottomClassFilter( new MethodLinker())); libraryClassPool.classesAccept(new BottomClassFilter( new MethodLinker())); // Create a visitor for marking the seeds. final KeepMarker keepMarker = new KeepMarker(); // All library classes and library class members remain unchanged. libraryClassPool.classesAccept(keepMarker); libraryClassPool.classesAccept(new AllMemberVisitor(keepMarker)); // Mark classes that have the DONT_OPTIMIZE flag set. programClassPool.classesAccept( new MultiClassVisitor( new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, keepMarker), new AllMemberVisitor( new MultiMemberVisitor( new MemberProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, keepMarker), new AllAttributeVisitor( new AttributeProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, keepMarker)) )) )); // We also keep all members that have the DONT_OPTIMIZE flag set on their code attribute. programClassPool.classesAccept( new AllMemberVisitor( new AllAttributeVisitor( new AttributeNameFilter(Attribute.CODE, new AttributeProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, new CodeAttributeToMethodVisitor(keepMarker)))))); // We also keep all classes that are involved in .class constructs. // We're not looking at enum classes though, so they can be simplified. programClassPool.classesAccept( new ClassAccessFilter(0, AccessConstants.ENUM, new AllMethodVisitor( new AllAttributeVisitor( new AllInstructionVisitor( new DotClassClassVisitor(keepMarker)))))); // We also keep all classes that are accessed dynamically. programClassPool.classesAccept( new AllConstantVisitor( new ConstantTagFilter(Constant.STRING, new ReferencedClassVisitor(keepMarker)))); // We also keep all class members that are accessed dynamically. programClassPool.classesAccept( new AllConstantVisitor( new ConstantTagFilter(Constant.STRING, new ReferencedMemberVisitor(keepMarker)))); // We also keep all bootstrap method signatures. programClassPool.classesAccept( new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, new AllAttributeVisitor( new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, new AllBootstrapMethodInfoVisitor( new BootstrapMethodHandleTraveler( new MethodrefTraveler( new ReferencedMemberVisitor(keepMarker)))))))); // We also keep classes and methods referenced from bootstrap // method arguments. programClassPool.classesAccept( new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, new AllAttributeVisitor( new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, new AllBootstrapMethodInfoVisitor( new AllBootstrapMethodArgumentVisitor( new MultiConstantVisitor( // Class constants refer to additional functional // interfaces (with LambdaMetafactory.altMetafactory). new ConstantTagFilter(Constant.CLASS, new ReferencedClassVisitor( new FunctionalInterfaceFilter( new ClassHierarchyTraveler(true, false, true, false, new MultiClassVisitor( keepMarker, new AllMethodVisitor( new MemberAccessFilter(AccessConstants.ABSTRACT, 0, keepMarker)) ))))), // Method handle constants refer to synthetic lambda // methods (with LambdaMetafactory.metafactory and // altMetafactory). new MethodrefTraveler( new ReferencedMemberVisitor(keepMarker))))))))); // We also keep the classes and abstract methods of functional // interfaces that are returned by dynamic method invocations. // These functional interfaces have to remain suitable for the // dynamic method invocations with LambdaMetafactory. programClassPool.classesAccept( new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, new AllConstantVisitor( new DynamicReturnedClassVisitor( new FunctionalInterfaceFilter( new ClassHierarchyTraveler(true, false, true, false, new MultiClassVisitor( keepMarker, new AllMethodVisitor( new MemberAccessFilter(AccessConstants.ABSTRACT, 0, keepMarker)) ))))))); // Attach some optimization info to all classes and class members, so // it can be filled out later. programClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); programClassPool.classesAccept(new AllMemberVisitor( new ProgramMemberOptimizationInfoSetter( false, configuration.optimizeConservatively))); if (configuration.assumeNoSideEffects != null) { // Create a visitor for marking classes and methods that don't have // any side effects. NoSideEffectClassMarker noSideEffectClassMarker = new NoSideEffectClassMarker(); NoSideEffectMethodMarker noSideEffectMethodMarker = new NoSideEffectMethodMarker(); ClassPoolVisitor classPoolVisitor = new ClassSpecificationVisitorFactory() .createClassPoolVisitor(configuration.assumeNoSideEffects, noSideEffectClassMarker, noSideEffectMethodMarker); // Mark the seeds. programClassPool.accept(classPoolVisitor); libraryClassPool.accept(classPoolVisitor); } if (configuration.assumeNoExternalSideEffects != null) { // Create a visitor for marking classes and methods that don't have // any external side effects. NoSideEffectClassMarker noSideEffectClassMarker = new NoSideEffectClassMarker(); NoExternalSideEffectMethodMarker noSideEffectMethodMarker = new NoExternalSideEffectMethodMarker(); ClassPoolVisitor classPoolVisitor = new ClassSpecificationVisitorFactory() .createClassPoolVisitor(configuration.assumeNoExternalSideEffects, noSideEffectClassMarker, noSideEffectMethodMarker); // Mark the seeds. programClassPool.accept(classPoolVisitor); libraryClassPool.accept(classPoolVisitor); } if (configuration.assumeNoEscapingParameters != null) { // Create a visitor for marking methods that don't let any // reference parameters escape. NoEscapingParametersMethodMarker noEscapingParametersMethodMarker = new NoEscapingParametersMethodMarker(); ClassPoolVisitor classPoolVisitor = new ClassSpecificationVisitorFactory() .createClassPoolVisitor(configuration.assumeNoEscapingParameters, null, noEscapingParametersMethodMarker); // Mark the seeds. programClassPool.accept(classPoolVisitor); libraryClassPool.accept(classPoolVisitor); } if (configuration.assumeNoExternalReturnValues != null) { // Create a visitor for marking methods that don't let any // reference parameters escape. NoExternalReturnValuesMethodMarker noExternalReturnValuesMethodMarker = new NoExternalReturnValuesMethodMarker(); ClassPoolVisitor classPoolVisitor = new ClassSpecificationVisitorFactory() .createClassPoolVisitor(configuration.assumeNoExternalReturnValues, null, noExternalReturnValuesMethodMarker); // Mark the seeds. programClassPool.accept(classPoolVisitor); libraryClassPool.accept(classPoolVisitor); } if (classMarkingFinal) { // Make classes final, whereever possible. programClassPool.classesAccept( new ClassFinalizer(classMarkingFinalCounter)); } if (methodMarkingFinal) { // Make methods final, whereever possible. programClassPool.classesAccept( new ClassAccessFilter(0, AccessConstants.INTERFACE, new AllMethodVisitor( new MethodFinalizer(methodMarkingFinalCounter)))); } // Give initial marks to read/written fields. side-effect methods, and // escaping parameters. final MutableBoolean mutableBoolean = new MutableBoolean(); if (fieldRemovalWriteonly) { // Mark fields that are read or written. The written flag is // currently only needed for the write-only counter later on. programClassPool.classesAccept( new AllMethodVisitor( new AllAttributeVisitor( new AllInstructionVisitor( new ReadWriteFieldMarker(mutableBoolean))))); } else { // Mark all fields as read and written. programClassPool.classesAccept( new AllFieldVisitor( new ReadWriteFieldMarker(mutableBoolean))); } // Mark methods based on their headers. programClassPool.classesAccept( new AllMethodVisitor( new OptimizationInfoMemberFilter( new MultiMemberVisitor( new SideEffectMethodMarker(configuration.optimizeConservatively), new ParameterEscapeMarker() )))); programClassPool.accept(new InfluenceFixpointVisitor( new SideEffectVisitorMarkerFactory(configuration.optimizeConservatively))); if (methodMarkingSynchronized) { // Mark all superclasses of escaping (kept) classes. programClassPool.classesAccept( new EscapingClassFilter( new ClassHierarchyTraveler(false, true, true, false, new EscapingClassMarker()))); ParallelAllClassVisitor.ClassVisitorFactory markingEscapingClassVisitor = new ParallelAllClassVisitor.ClassVisitorFactory() { public ClassVisitor createClassVisitor() { return new AllMethodVisitor( new AllAttributeVisitor( new EscapingClassMarker())); } }; // Mark classes that escape to the heap. programClassPool.accept( new TimedClassPoolVisitor("Marking escaping classes", new ParallelAllClassVisitor( markingEscapingClassVisitor))); // Desynchronize all non-static methods whose classes don't escape. programClassPool.classesAccept( new EscapingClassFilter(null, new AllMethodVisitor( new OptimizationInfoMemberFilter( new MemberAccessFilter(AccessConstants.SYNCHRONIZED, AccessConstants.STATIC, new MultiMemberVisitor( new MemberAccessFlagCleaner(AccessConstants.SYNCHRONIZED), methodMarkingSynchronizedCounter )))))); } if (fieldRemovalWriteonly) { // Count the write-only fields. programClassPool.classesAccept( new AllFieldVisitor( new WriteOnlyFieldFilter(fieldRemovalWriteonlyCounter))); } if (classUnboxingEnum) { ClassCounter counter = new ClassCounter(); // Mark all final enums that qualify as simple enums. programClassPool.classesAccept( new ClassAccessFilter(AccessConstants.FINAL | AccessConstants.ENUM, 0, new OptimizationInfoClassFilter( new SimpleEnumClassChecker()))); // Count the preliminary number of simple enums. programClassPool.classesAccept( new SimpleEnumFilter(counter)); // Only continue checking simple enums if there are any candidates. if (counter.getCount() > 0) { // Unmark all simple enums that are explicitly used as objects. programClassPool.classesAccept( new SimpleEnumUseChecker()); // Unmark all simple enums that are used in descriptors of // kept class members. Changing their names could upset // the name parameters of invokedynamic instructions. programClassPool.classesAccept( new SimpleEnumFilter(null, new AllMemberVisitor( new KeptMemberFilter( new MemberDescriptorReferencedClassVisitor( new OptimizationInfoClassFilter( new SimpleEnumMarker(false))))))); // Count the definitive number of simple enums. programClassPool.classesAccept( new SimpleEnumFilter(classUnboxingEnumCounter)); // Only start handling simple enums if there are any. if (classUnboxingEnumCounter.getCount() > 0) { // Simplify the use of the enum classes in code. programClassPool.accept( new TimedClassPoolVisitor("Simplify use of simple enums", new AllMethodVisitor( new AllAttributeVisitor( new SimpleEnumUseSimplifier())))); // Simplify the static initializers of simple enum classes. programClassPool.classesAccept( new SimpleEnumFilter( new SimpleEnumClassSimplifier())); // Simplify the use of the enum classes in descriptors. programClassPool.classesAccept( new SimpleEnumDescriptorSimplifier()); // Update references to class members with simple enum classes. programClassPool.classesAccept(new MemberReferenceFixer(configuration.android)); } } } // Mark all used parameters, including the 'this' parameters. ParallelAllClassVisitor.ClassVisitorFactory markingUsedParametersClassVisitor = new ParallelAllClassVisitor.ClassVisitorFactory() { public ClassVisitor createClassVisitor() { return new AllMethodVisitor( new OptimizationInfoMemberFilter( new ParameterUsageMarker(!methodMarkingStatic, !methodRemovalParameter))); } }; programClassPool.accept( new TimedClassPoolVisitor("Marking used parameters", new ParallelAllClassVisitor( markingUsedParametersClassVisitor))); // Mark all parameters of referenced methods in methods whose code must // be kept. This prevents shrinking of method descriptors which may not // be propagated correctly otherwise. programClassPool.accept( new TimedClassPoolVisitor("Marking used parameters in kept code attributes", new AllClassVisitor( new AllMethodVisitor( new OptimizationInfoMemberFilter( null, // visit all methods that are kept new AllAttributeVisitor( new OptimizationCodeAttributeFilter( null, // visit all code attributes that are kept new AllInstructionVisitor( new InstructionConstantVisitor( new ConstantTagFilter(new int[] { Constant.METHODREF, Constant.INTERFACE_METHODREF }, new ReferencedMemberVisitor( new OptimizationInfoMemberFilter( // Mark all parameters including "this" of referenced methods new ParameterUsageMarker(true, true, false)))))))) ))))); // Mark parameter usage based on Kotlin Context receivers KotlinContextReceiverUsageMarker kotlinContextReceiverUsageMarker = new KotlinContextReceiverUsageMarker(); programClassPool.accept( new AllClassVisitor( new ReferencedKotlinMetadataVisitor( new MultiKotlinMetadataVisitor( kotlinContextReceiverUsageMarker, new AllFunctionVisitor(kotlinContextReceiverUsageMarker), new AllPropertyVisitor(kotlinContextReceiverUsageMarker) ))) ); // Perform partial evaluation for filling out fields, method parameters, // and method return values, so they can be propagated. if (fieldSpecializationType || methodSpecializationParametertype || methodSpecializationReturntype || fieldPropagationValue || methodPropagationParameter || methodPropagationReturnvalue || classMergingWrapper) { // We'll create values to be stored with fields, method parameters, // and return values. ValueFactory valueFactory = new BasicRangeValueFactory(); ValueFactory detailedValueFactory = new DetailedArrayValueFactory(); InvocationUnit storingInvocationUnit = new StoringInvocationUnit(valueFactory, fieldSpecializationType || fieldPropagationValue, methodSpecializationParametertype || methodPropagationParameter || classMergingWrapper, methodSpecializationReturntype || methodPropagationReturnvalue); // Evaluate synthetic classes in more detail, notably to propagate // the arrays of the classes generated for enum switch statements. programClassPool.classesAccept( new ClassAccessFilter(AccessConstants.SYNTHETIC, 0, new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Filling out fields, method parameters, and return values in synthetic classes", new PartialEvaluator(detailedValueFactory, storingInvocationUnit, false)))))); // Evaluate non-synthetic classes. We may need to evaluate all // casts, to account for downcasts when specializing descriptors. ParallelAllClassVisitor.ClassVisitorFactory fillingOutValuesClassVisitor = new ParallelAllClassVisitor.ClassVisitorFactory() { public ClassVisitor createClassVisitor() { ValueFactory valueFactory = new ParticularValueFactory(); InvocationUnit storingInvocationUnit = new StoringInvocationUnit(valueFactory, fieldSpecializationType || fieldPropagationValue, methodSpecializationParametertype || methodPropagationParameter || classMergingWrapper, methodSpecializationReturntype || methodPropagationReturnvalue); return new ClassAccessFilter(0, AccessConstants.SYNTHETIC, new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Filling out fields, method parameters, and return values", new PartialEvaluator(valueFactory, storingInvocationUnit, fieldSpecializationType || methodSpecializationParametertype || methodSpecializationReturntype))))); } }; programClassPool.accept( new TimedClassPoolVisitor("Filling out values in non-synthetic classes", new ParallelAllClassVisitor( fillingOutValuesClassVisitor))); if (fieldSpecializationType || methodSpecializationParametertype || methodSpecializationReturntype) { // Specialize class member descriptors, based on partial evaluation. programClassPool.classesAccept( new AllMemberVisitor( new OptimizationInfoMemberFilter( new MemberDescriptorSpecializer(fieldSpecializationType, methodSpecializationParametertype, methodSpecializationReturntype, fieldSpecializationTypeCounter, methodSpecializationParametertypeCounter, methodSpecializationReturntypeCounter)))); if (fieldSpecializationTypeCounter.getCount() > 0 || methodSpecializationParametertypeCounter.getCount() > 0 || methodSpecializationReturntypeCounter.getCount() > 0) { // Fix all references to specialized members. programClassPool.classesAccept(new MemberReferenceFixer(configuration.android)); } } if (configuration.assumeValues != null) { // Create a visitor for setting assumed values. ClassPoolVisitor classPoolVisitor = new AssumeClassSpecificationVisitorFactory(valueFactory) .createClassPoolVisitor(configuration.assumeValues, null, new MultiMemberVisitor()); // Set the assumed values. programClassPool.accept(classPoolVisitor); libraryClassPool.accept(classPoolVisitor); } if (fieldPropagationValue) { // Count the constant fields. programClassPool.classesAccept( new AllFieldVisitor( new ConstantMemberFilter(fieldPropagationValueCounter))); } if (methodPropagationParameter) { // Count the constant method parameters. programClassPool.classesAccept( new AllMethodVisitor( new ConstantParameterFilter(methodPropagationParameterCounter))); } if (methodPropagationReturnvalue) { // Count the constant method return values. programClassPool.classesAccept( new AllMethodVisitor( new ConstantMemberFilter(methodPropagationReturnvalueCounter))); } if (classUnboxingEnumCounter.getCount() > 0) { // Propagate the simple enum constant counts. programClassPool.classesAccept( new SimpleEnumFilter( new SimpleEnumArrayPropagator())); } if (codeSimplificationAdvanced) { // Fill out constants into the arrays of synthetic classes, // notably the arrays of the classes generated for enum switch // statements. InvocationUnit loadingInvocationUnit = new LoadingInvocationUnit(valueFactory, fieldPropagationValue, methodPropagationParameter, methodPropagationReturnvalue); programClassPool.classesAccept( new ClassAccessFilter(AccessConstants.SYNTHETIC, 0, new AllMethodVisitor( new AllAttributeVisitor( new PartialEvaluator(valueFactory, loadingInvocationUnit, false))))); } } if (codeSimplificationAdvanced) { ParallelAllClassVisitor.ClassVisitorFactory simplifyingCodeVisitor = new ParallelAllClassVisitor.ClassVisitorFactory() { public ClassVisitor createClassVisitor() { // Perform partial evaluation again, now loading any previously stored // values for fields, method parameters, and method return values. ValueFactory valueFactory = new IdentifiedValueFactory(); SimplifiedInvocationUnit loadingInvocationUnit = new LoadingInvocationUnit(valueFactory, fieldPropagationValue, methodPropagationParameter, methodPropagationReturnvalue); return new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Simplifying code", new OptimizationCodeAttributeFilter( new EvaluationSimplifier( new PartialEvaluator(valueFactory, loadingInvocationUnit, false), codeSimplificationAdvancedCounter, configuration.optimizeConservatively))))); } }; // Simplify based on partial evaluation, propagating constant // field values, method parameter values, and return values. programClassPool.accept( new TimedClassPoolVisitor("Simplifying code", new ParallelAllClassVisitor( simplifyingCodeVisitor))); } if (codeRemovalAdvanced) { ParallelAllClassVisitor.ClassVisitorFactory shrinkingCodeVisitor = new ParallelAllClassVisitor.ClassVisitorFactory() { public ClassVisitor createClassVisitor() { // Perform partial evaluation again, now loading any previously stored // values for fields, method parameters, and method return values. ValueFactory valueFactory = new IdentifiedValueFactory(); SimplifiedInvocationUnit loadingInvocationUnit = new LoadingInvocationUnit(valueFactory, fieldPropagationValue, methodPropagationParameter, methodPropagationReturnvalue); // Trace the construction of reference values. ReferenceTracingValueFactory referenceTracingValueFactory = new ReferenceTracingValueFactory(valueFactory); return new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Shrinking code", new OptimizationCodeAttributeFilter( new EvaluationShrinker( new InstructionUsageMarker( new PartialEvaluator(referenceTracingValueFactory, new ParameterTracingInvocationUnit(loadingInvocationUnit), !codeSimplificationAdvanced, referenceTracingValueFactory), true, configuration.optimizeConservatively), true, deletedCounter, addedCounter))))); } }; // Remove code based on partial evaluation, also removing unused // parameters from method invocations, and making methods static // if possible. programClassPool.accept( new TimedClassPoolVisitor("Shrinking code", new ParallelAllClassVisitor( shrinkingCodeVisitor))); } if (methodRemovalParameter) { // Shrink the parameters in the method descriptors. programClassPool.classesAccept( new AllMethodVisitor( new UnusedParameterMethodFilter( new OptimizationInfoMemberFilter( new MethodDescriptorShrinker(methodRemovalParameterCounter1))))); } if (methodMarkingStatic) { // Make all non-static methods that don't require the 'this' // parameter static. programClassPool.classesAccept( new AllMethodVisitor( new OptimizationInfoMemberFilter( new MemberAccessFilter(0, AccessConstants.STATIC, new MethodStaticizer(methodMarkingStaticCounter))))); } if (methodRemovalParameterCounter1.getCount() > 0) { // Fix all references to class members. // This operation also updates the stack sizes. programClassPool.classesAccept(new MemberReferenceFixer(configuration.android)); // Remove unused bootstrap method arguments. programClassPool.classesAccept( new AllAttributeVisitor( new AllBootstrapMethodInfoVisitor( new BootstrapMethodArgumentShrinker()))); } if (methodRemovalParameterCounter1.getCount() > 0 || methodMarkingPrivate || // Methods are only marked private later on. //methodMarkingPrivateCounter .getCount() > 0 || methodMarkingStaticCounter .getCount() > 0) { // Remove all unused parameters from the corresponding byte code, // shifting all remaining variables. // This operation also updates the local variable frame sizes. programClassPool.classesAccept( new AllMethodVisitor( new UnusedParameterMethodFilter( new AllAttributeVisitor( new ParameterShrinker(methodRemovalParameterCounter2))))); // Remove all unused parameters in the optimization info. programClassPool.classesAccept( new AllMethodVisitor( new UnusedParameterMethodFilter( new AllAttributeVisitor( new UnusedParameterOptimizationInfoUpdater(new ProcessingFlagSetter(ProcessingFlags.MODIFIED)))))); } else if (codeRemovalAdvanced) { // Just update the local variable frame sizes. programClassPool.classesAccept( new AllMethodVisitor( new AllAttributeVisitor( new OptimizationCodeAttributeFilter( new StackSizeUpdater())))); } if (methodRemovalParameter && methodRemovalParameterCounter2.getCount() > 0) { // Tweak the descriptors of duplicate initializers, due to removed // method parameters. programClassPool.classesAccept( new AllMethodVisitor( new DuplicateInitializerFixer(initializerFixCounter1))); if (initializerFixCounter1.getCount() > 0) { // Fix all invocations of tweaked initializers. programClassPool.classesAccept( new AllMethodVisitor( new AllAttributeVisitor( new DuplicateInitializerInvocationFixer(addedCounter)))); // Fix all references to tweaked initializers. programClassPool.classesAccept(new MemberReferenceFixer(configuration.android)); } } // Mark all classes with package visible members. // Mark all exception catches of methods. // Count all method invocations. // Mark super invocations and other access of methods. StackSizeComputer stackSizeComputer = new StackSizeComputer(); programClassPool.accept( new TimedClassPoolVisitor("Marking method and referenced class properties", new MultiClassVisitor( // Mark classes. new OptimizationInfoClassFilter( new MultiClassVisitor( new PackageVisibleMemberContainingClassMarker(), new WrapperClassMarker(), new AllConstantVisitor( new PackageVisibleMemberInvokingClassMarker()), new AllMemberVisitor( new ContainsConstructorsMarker()) )), // Mark methods. new AllMethodVisitor( new OptimizationInfoMemberFilter( new AllAttributeVisitor( new DebugAttributeVisitor("Marking method properties", new MultiAttributeVisitor( stackSizeComputer, new CatchExceptionMarker(), new AllInstructionVisitor( new MultiInstructionVisitor( new SuperInvocationMarker(), new DynamicInvocationMarker(), new BackwardBranchMarker(), new AccessMethodMarker(), new SynchronizedBlockMethodMarker(), new FinalFieldAssignmentMarker(), new NonEmptyStackReturnMarker(stackSizeComputer) )) ))))), // Mark referenced classes and methods. new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Marking referenced class properties", new MultiAttributeVisitor( new AllExceptionInfoVisitor( new ExceptionHandlerConstantVisitor( new ReferencedClassVisitor( new OptimizationInfoClassFilter( new CaughtClassMarker())))), new AllInstructionVisitor( new MultiInstructionVisitor( new InstantiationClassMarker(), new InstanceofClassMarker(), new DotClassMarker(), new MethodInvocationMarker() )) )))) ))); if (classMergingWrapper) { // Merge wrapper classes into their wrapped classes. programClassPool.accept( new TimedClassPoolVisitor("Merging wrapper classes", // Exclude injected classes - they might not end up in the output. new InjectedClassFilter(null, new WrapperClassMerger(configuration.allowAccessModification, classMergingWrapperCounter)))); if (classMergingWrapperCounter.getCount() > 0) { // Fix all uses of wrapper classes. programClassPool.classesAccept( new RetargetedClassFilter(null, new AllMethodVisitor( new AllAttributeVisitor( new WrapperClassUseSimplifier())))); } } if (classMergingVertical) { // Merge subclasses up into their superclasses or // merge interfaces down into their implementing classes. programClassPool.accept( new TimedClassPoolVisitor("Merging classes vertically", // Exclude injected classes - they might not end up in the output. new InjectedClassFilter(null, new VerticalClassMerger(configuration.allowAccessModification, configuration.mergeInterfacesAggressively, classMergingVerticalCounter)))); } if (classMergingHorizontal) { long start = System.currentTimeMillis(); Set forbiddenClassNames = new HashSet<>(); // Extra Data Entries are not allowed to be merged extraDataEntryNameMap.getAllExtraDataEntryNames() .stream() .filter(s -> s.endsWith(".class")) .map(s -> s.substring(0, s.length() - 6)) .forEachOrdered(forbiddenClassNames::add); programClassPool.accept( new HorizontalClassMerger(configuration.allowAccessModification, configuration.mergeInterfacesAggressively, forbiddenClassNames, classMergingHorizontalCounter) ); long end = System.currentTimeMillis(); logger.trace("Merging classes horizontally.................... took: %6d ms", (end - start)); } if (classMergingVerticalCounter .getCount() > 0 || classMergingHorizontalCounter.getCount() > 0 || classMergingWrapperCounter .getCount() > 0) { // Clean up inner class attributes to avoid loops. programClassPool.classesAccept(new RetargetedInnerClassAttributeRemover()); // Update references to merged classes: first the referenced // classes, then the various actual descriptors. // Leave retargeted classes themselves unchanged and valid, // in case they aren't shrunk later on. programClassPool.classesAccept(new RetargetedClassFilter(null, new TargetClassChanger())); programClassPool.classesAccept(new RetargetedClassFilter(null, new ClassReferenceFixer(true))); programClassPool.classesAccept(new RetargetedClassFilter(null, new MemberReferenceFixer(configuration.android))); if (configuration.allowAccessModification) { // Fix the access flags of referenced merged classes and their // class members. programClassPool.classesAccept(new AccessFixer()); } // Fix the access flags of the inner classes information. // DGD-63: don't change the access flags of inner classes // that have not been renamed (Guice). programClassPool.classesAccept( new KeptClassFilter(null, new AllAttributeVisitor( new AllInnerClassesInfoVisitor( new InnerClassesAccessFixer())))); // Tweak the descriptors of duplicate initializers, due to merged // parameter classes. programClassPool.classesAccept( new AllMethodVisitor( new DuplicateInitializerFixer(initializerFixCounter2))); if (initializerFixCounter2.getCount() > 0) { // Fix all invocations of tweaked initializers. programClassPool.classesAccept( new AllMethodVisitor( new AllAttributeVisitor( new DuplicateInitializerInvocationFixer(addedCounter)))); // Fix all references to tweaked initializers. programClassPool.classesAccept(new MemberReferenceFixer(configuration.android)); } } if (methodInliningUnique) { // Inline methods that are only invoked once. programClassPool.accept( new TimedClassPoolVisitor("Inlining single methods", new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Inlining single methods", new OptimizationCodeAttributeFilter( new SingleInvocationMethodInliner(configuration.microEdition, configuration.android, configuration.allowAccessModification, methodInliningUniqueCounter))))))); } if (methodInliningShort) { // Inline short methods. programClassPool.accept( new TimedClassPoolVisitor("Inlining short methods", new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Inlining short methods", new OptimizationCodeAttributeFilter( new ShortMethodInliner(configuration.microEdition, configuration.android, configuration.allowAccessModification, methodInliningShortCounter))))))); } if (methodInliningTailrecursion) { // Simplify tail recursion calls. programClassPool.accept( new TimedClassPoolVisitor("Simplifying tail recursion", new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Simplifying tail recursion", new OptimizationCodeAttributeFilter( new TailRecursionSimplifier(methodInliningTailrecursionCounter))))))); } if (fieldMarkingPrivate || methodMarkingPrivate) { // Mark all class members that can not be made private. programClassPool.classesAccept(new NonPrivateMemberMarker()); } if (fieldMarkingPrivate) { // Make all non-private fields private, whereever possible. programClassPool.classesAccept( new ClassAccessFilter(0, AccessConstants.INTERFACE, new AllFieldVisitor( new MemberAccessFilter(0, AccessConstants.PRIVATE, new MemberPrivatizer(fieldMarkingPrivateCounter))))); } if (methodMarkingPrivate) { // Make all non-private methods private, whereever possible. programClassPool.classesAccept( new ClassAccessFilter(0, AccessConstants.INTERFACE, new AllMethodVisitor( new MemberAccessFilter(0, AccessConstants.PRIVATE, new MemberPrivatizer(methodMarkingPrivateCounter))))); } if ((methodInliningUniqueCounter .getCount() > 0 || methodInliningShortCounter .getCount() > 0 || methodInliningTailrecursionCounter.getCount() > 0) && configuration.allowAccessModification) { // Fix the access flags of referenced classes and class members, // for MethodInliner. programClassPool.classesAccept(new AccessFixer()); } if (methodRemovalParameterCounter2.getCount() > 0 || classMergingVerticalCounter .getCount() > 0 || classMergingHorizontalCounter .getCount() > 0 || classMergingWrapperCounter .getCount() > 0 || methodMarkingPrivateCounter .getCount() > 0 || ((methodInliningUniqueCounter .getCount() > 0 || methodInliningShortCounter .getCount() > 0 || methodInliningTailrecursionCounter.getCount() > 0) && configuration.allowAccessModification)) { // Fix invocations of interface methods, or methods that have become // non-abstract or private, and of methods that have moved to a // different package. programClassPool.classesAccept( new AllMemberVisitor( new AllAttributeVisitor( new MethodInvocationFixer()))); } if (codeMerging) { // Share common blocks of code at branches. programClassPool.accept( new TimedClassPoolVisitor("Sharing common code", new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Sharing common code", new OptimizationCodeAttributeFilter( new GotoCommonCodeReplacer(codeMergingCounter))))))); } if (codeSimplificationPeephole) { ParallelAllClassVisitor.ClassVisitorFactory peepHoleOptimizer = new ParallelAllClassVisitor.ClassVisitorFactory() { public ClassVisitor createClassVisitor() { // Create a branch target marker and a code attribute editor that can // be reused for all code attributes. BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); InstructionSequenceConstants sequences = new InstructionSequenceConstants(programClassPool, libraryClassPool); List peepholeOptimizations = createPeepholeOptimizations(configuration, sequences, branchTargetFinder, codeAttributeEditor, codeSimplificationVariableCounter, codeSimplificationArithmeticCounter, codeSimplificationCastCounter, codeSimplificationFieldCounter, codeSimplificationBranchCounter, codeSimplificationObjectCounter, codeSimplificationStringCounter, codeSimplificationMathCounter, codeSimplificationAndroidMathCounter, fieldGeneralizationClassCounter, methodGeneralizationClassCounter); // Convert the list into an array. InstructionVisitor[] peepholeOptimizationsArray = new InstructionVisitor[peepholeOptimizations.size()]; peepholeOptimizations.toArray(peepholeOptimizationsArray); return new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Peephole optimizations", new OptimizationCodeAttributeFilter( new PeepholeEditor(branchTargetFinder, codeAttributeEditor, new MultiInstructionVisitor( peepholeOptimizationsArray)))))); } }; // Perform the peephole optimisations. programClassPool.accept( new TimedClassPoolVisitor("Peephole optimizations", new ParallelAllClassVisitor( peepHoleOptimizer))); } if (codeRemovalException) { // Remove unnecessary exception handlers. programClassPool.accept( new TimedClassPoolVisitor("Unreachable exception removal", new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Unreachable exception removal", new OptimizationCodeAttributeFilter( new UnreachableExceptionRemover(codeRemovalExceptionCounter))))))); } if (codeRemovalSimple) { // Remove unreachable code. programClassPool.accept( new TimedClassPoolVisitor("Unreachable code removal", new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Unreachable code removal", new OptimizationCodeAttributeFilter( new UnreachableCodeRemover(deletedCounter))))))); } if (codeRemovalVariable) { // Remove all unused local variables. programClassPool.accept( new TimedClassPoolVisitor("Variable shrinking", new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Variable shrinking", new OptimizationCodeAttributeFilter( new VariableShrinker(codeRemovalVariableCounter))))))); } if (codeAllocationVariable) { ParallelAllClassVisitor.ClassVisitorFactory optimizingVariablesVisitor = new ParallelAllClassVisitor.ClassVisitorFactory() { public ClassVisitor createClassVisitor() { return new AllMethodVisitor( new AllAttributeVisitor( new DebugAttributeVisitor("Variable optimizations", new OptimizationCodeAttributeFilter( new VariableOptimizer(false, codeAllocationVariableCounter))))); } }; // Optimize the variables. programClassPool.accept( new TimedClassPoolVisitor("Variable optimizations", new ParallelAllClassVisitor( optimizingVariablesVisitor))); } // Remove unused constants. programClassPool.accept( new TimedClassPoolVisitor("Shrinking constant pool", new ConstantPoolShrinker())); int classMarkingFinalCount = classMarkingFinalCounter .getCount(); int classUnboxingEnumCount = classUnboxingEnumCounter .getCount(); int classMergingVerticalCount = classMergingVerticalCounter .getCount(); int classMergingHorizontalCount = classMergingHorizontalCounter .getCount(); int classMergingWrapperCount = classMergingWrapperCounter .getCount(); int fieldRemovalWriteonlyCount = fieldRemovalWriteonlyCounter .getCount(); int fieldMarkingPrivateCount = fieldMarkingPrivateCounter .getCount(); int fieldGeneralizationClassCount = fieldGeneralizationClassCounter .getCount(); int fieldSpecializationTypeCount = fieldSpecializationTypeCounter .getCount(); int fieldPropagationValueCount = fieldPropagationValueCounter .getCount(); int methodMarkingPrivateCount = methodMarkingPrivateCounter .getCount(); int methodMarkingStaticCount = methodMarkingStaticCounter .getCount(); int methodMarkingFinalCount = methodMarkingFinalCounter .getCount(); int methodMarkingSynchronizedCount = methodMarkingSynchronizedCounter .getCount(); int methodRemovalParameterCount1 = methodRemovalParameterCounter1 .getCount() - initializerFixCounter1.getCount() - initializerFixCounter2.getCount(); int methodRemovalParameterCount2 = methodRemovalParameterCounter2 .getCount() - methodMarkingStaticCounter.getCount() - initializerFixCounter1.getCount() - initializerFixCounter2.getCount(); int methodGeneralizationClassCount = methodGeneralizationClassCounter .getCount(); int methodSpecializationParametertypeCount = methodSpecializationParametertypeCounter.getCount(); int methodSpecializationReturntypeCount = methodSpecializationReturntypeCounter .getCount(); int methodPropagationParameterCount = methodPropagationParameterCounter .getCount(); int methodPropagationReturnvalueCount = methodPropagationReturnvalueCounter .getCount(); int methodInliningShortCount = methodInliningShortCounter .getCount(); int methodInliningUniqueCount = methodInliningUniqueCounter .getCount(); int methodInliningTailrecursionCount = methodInliningTailrecursionCounter .getCount(); int codeMergingCount = codeMergingCounter .getCount(); int codeSimplificationVariableCount = codeSimplificationVariableCounter .getCount(); int codeSimplificationArithmeticCount = codeSimplificationArithmeticCounter .getCount(); int codeSimplificationCastCount = codeSimplificationCastCounter .getCount(); int codeSimplificationFieldCount = codeSimplificationFieldCounter .getCount(); int codeSimplificationBranchCount = codeSimplificationBranchCounter .getCount(); int codeSimplificationObjectCount = codeSimplificationObjectCounter .getCount(); int codeSimplificationStringCount = codeSimplificationStringCounter .getCount(); int codeSimplificationMathCount = codeSimplificationMathCounter .getCount(); int codeSimplificationAndroidMathCount = codeSimplificationAndroidMathCounter .getCount(); int codeSimplificationAdvancedCount = codeSimplificationAdvancedCounter .getCount(); int codeRemovalCount = deletedCounter .getCount() - addedCounter.getCount(); int codeRemovalVariableCount = codeRemovalVariableCounter .getCount(); int codeRemovalExceptionCount = codeRemovalExceptionCounter .getCount(); int codeAllocationVariableCount = codeAllocationVariableCounter .getCount(); // Forget about constant fields, parameters, and return values, if they // didn't lead to any useful optimizations. We want to avoid fruitless // additional optimization passes. if (codeSimplificationAdvancedCount == 0) { fieldPropagationValueCount = 0; methodPropagationParameterCount = 0; methodPropagationReturnvalueCount = 0; } logger.info(" Number of finalized classes: {}{}", classMarkingFinalCount, disabled(classMarkingFinal)); logger.info(" Number of unboxed enum classes: {}{}", classUnboxingEnumCount, disabled(classUnboxingEnum)); logger.info(" Number of vertically merged classes: {}{}", classMergingVerticalCount, disabled(classMergingVertical)); logger.info(" Number of horizontally merged classes: {}{}", classMergingHorizontalCount, disabled(classMergingHorizontal)); logger.info(" Number of merged wrapper classes: {}{}", classMergingWrapperCount, disabled(classMergingWrapper)); logger.info(" Number of removed write-only fields: {}{}", fieldRemovalWriteonlyCount, disabled(fieldRemovalWriteonly)); logger.info(" Number of privatized fields: {}{}", fieldMarkingPrivateCount, disabled(fieldMarkingPrivate)); logger.info(" Number of generalized field accesses: {}{}", fieldGeneralizationClassCount, disabled(fieldGeneralizationClass)); logger.info(" Number of specialized field types: {}{}", fieldSpecializationTypeCount, disabled(fieldSpecializationType)); logger.info(" Number of inlined constant fields: {}{}", fieldPropagationValueCount, disabled(fieldPropagationValue)); logger.info(" Number of privatized methods: {}{}", methodMarkingPrivateCount, disabled(methodMarkingPrivate)); logger.info(" Number of staticized methods: {}{}", methodMarkingStaticCount, disabled(methodMarkingStatic)); logger.info(" Number of finalized methods: {}{}", methodMarkingFinalCount, disabled(methodMarkingFinal)); logger.info(" Number of desynchronized methods: {}{}", methodMarkingSynchronizedCount, disabled(methodMarkingSynchronized)); logger.info(" Number of simplified method signatures: {}{}", methodRemovalParameterCount1, disabled(methodRemovalParameter)); logger.info(" Number of removed method parameters: {}{}", methodRemovalParameterCount2, disabled(methodRemovalParameter)); logger.info(" Number of generalized method invocations: {}{}", methodGeneralizationClassCount, disabled(methodGeneralizationClass)); logger.info(" Number of specialized method parameter types: {}{}", methodSpecializationParametertypeCount, disabled(methodSpecializationParametertype)); logger.info(" Number of specialized method return types: {}{}", methodSpecializationReturntypeCount, disabled(methodSpecializationReturntype)); logger.info(" Number of inlined constant parameters: {}{}", methodPropagationParameterCount, disabled(methodPropagationParameter)); logger.info(" Number of inlined constant return values: {}{}", methodPropagationReturnvalueCount, disabled(methodPropagationReturnvalue)); logger.info(" Number of inlined short method calls: {}{}", methodInliningShortCount, disabled(methodInliningShort)); logger.info(" Number of inlined unique method calls: {}{}", methodInliningUniqueCount, disabled(methodInliningUnique)); logger.info(" Number of inlined tail recursion calls: {}{}", methodInliningTailrecursionCount, disabled(methodInliningTailrecursion)); logger.info(" Number of merged code blocks: {}{}", codeMergingCount, disabled(codeMerging)); logger.info(" Number of variable peephole optimizations: {}{}", codeSimplificationVariableCount, disabled(codeSimplificationVariable)); logger.info(" Number of arithmetic peephole optimizations: {}{}", codeSimplificationArithmeticCount, disabled(codeSimplificationArithmetic)); logger.info(" Number of cast peephole optimizations: {}{}", codeSimplificationCastCount, disabled(codeSimplificationCast)); logger.info(" Number of field peephole optimizations: {}{}", codeSimplificationFieldCount, disabled(codeSimplificationField)); logger.info(" Number of branch peephole optimizations: {}{}", codeSimplificationBranchCount, disabled(codeSimplificationBranch)); logger.info(" Number of object peephole optimizations: {}{}", codeSimplificationObjectCount, disabled(codeSimplificationObject)); logger.info(" Number of string peephole optimizations: {}{}", codeSimplificationStringCount, disabled(codeSimplificationString)); logger.info(" Number of math peephole optimizations: {}{}", codeSimplificationMathCount, disabled(codeSimplificationMath)); if (configuration.android) { logger.info(" Number of Android math peephole optimizations: {}{}", codeSimplificationAndroidMathCount, disabled(codeSimplificationMath)); } logger.info(" Number of simplified instructions: {}{}", codeSimplificationAdvancedCount, disabled(codeSimplificationAdvanced)); logger.info(" Number of removed instructions: {}{}", codeRemovalCount, disabled(codeRemovalAdvanced)); logger.info(" Number of removed local variables: {}{}", codeRemovalVariableCount, disabled(codeRemovalVariable)); logger.info(" Number of removed exception blocks: {}{}", codeRemovalExceptionCount, disabled(codeRemovalException)); logger.info(" Number of optimized local variable frames: {}{}", codeAllocationVariableCount, disabled(codeAllocationVariable)); moreOptimizationsPossible = classMarkingFinalCount > 0 || classUnboxingEnumCount > 0 || classMergingVerticalCount > 0 || classMergingHorizontalCount > 0 || classMergingWrapperCount > 0 || fieldRemovalWriteonlyCount > 0 || // TODO: The write-only field counter may be optimistic about removal. fieldMarkingPrivateCount > 0 || methodMarkingPrivateCount > 0 || methodMarkingStaticCount > 0 || methodMarkingFinalCount > 0 || fieldGeneralizationClassCount > 0 || fieldSpecializationTypeCount > 0 || fieldPropagationValueCount > 0 || methodRemovalParameterCount1 > 0 || methodRemovalParameterCount2 > 0 || methodGeneralizationClassCount > 0 || methodSpecializationParametertypeCount > 0 || methodSpecializationReturntypeCount > 0 || methodPropagationParameterCount > 0 || methodPropagationReturnvalueCount > 0 || methodInliningShortCount > 0 || methodInliningUniqueCount > 0 || methodInliningTailrecursionCount > 0 || codeMergingCount > 0 || codeSimplificationVariableCount > 0 || codeSimplificationArithmeticCount > 0 || codeSimplificationCastCount > 0 || codeSimplificationFieldCount > 0 || codeSimplificationBranchCount > 0 || codeSimplificationObjectCount > 0 || codeSimplificationStringCount > 0 || codeSimplificationMathCount > 0 || codeSimplificationAndroidMathCount > 0 || codeSimplificationAdvancedCount > 0 || codeRemovalCount > 0 || codeRemovalVariableCount > 0 || codeRemovalExceptionCount > 0 || codeAllocationVariableCount > 0; } private List createPeepholeOptimizations(Configuration configuration, InstructionSequenceConstants sequences, BranchTargetFinder branchTargetFinder, CodeAttributeEditor codeAttributeEditor, InstructionCounter codeSimplificationVariableCounter, InstructionCounter codeSimplificationArithmeticCounter, InstructionCounter codeSimplificationCastCounter, InstructionCounter codeSimplificationFieldCounter, InstructionCounter codeSimplificationBranchCounter, InstructionCounter codeSimplificationObjectCounter, InstructionCounter codeSimplificationStringCounter, InstructionCounter codeSimplificationMathCounter, InstructionCounter codeSimplificationAndroidMathCounter, InstructionCounter fieldGeneralizationClassCounter, InstructionCounter methodGeneralizationClassCounter) { List peepholeOptimizations = new ArrayList<>(); if (codeSimplificationVariable) { // Peephole optimizations involving local variables. peepholeOptimizations.add(new InstructionSequencesReplacer(sequences.CONSTANTS, sequences.VARIABLE_SEQUENCES, branchTargetFinder, codeAttributeEditor, codeSimplificationVariableCounter)); } if (codeSimplificationArithmetic) { // Peephole optimizations involving arithmetic operations. peepholeOptimizations.add(new InstructionSequencesReplacer(sequences.CONSTANTS, sequences.ARITHMETIC_SEQUENCES, branchTargetFinder, codeAttributeEditor, codeSimplificationArithmeticCounter)); } if (codeSimplificationCast) { // Peephole optimizations involving cast operations. peepholeOptimizations.add(new InstructionSequencesReplacer(sequences.CONSTANTS, sequences.CAST_SEQUENCES, branchTargetFinder, codeAttributeEditor, codeSimplificationCastCounter)); } if (codeSimplificationField) { // Peephole optimizations involving fields. peepholeOptimizations.add(new InstructionSequencesReplacer(sequences.CONSTANTS, sequences.FIELD_SEQUENCES, branchTargetFinder, codeAttributeEditor, codeSimplificationFieldCounter)); } if (codeSimplificationBranch) { // Peephole optimizations involving branches. peepholeOptimizations.add(new InstructionSequencesReplacer(sequences.CONSTANTS, sequences.BRANCH_SEQUENCES, branchTargetFinder, codeAttributeEditor, codeSimplificationBranchCounter)); } if (codeSimplificationObject) { // Peephole optimizations involving objects. peepholeOptimizations.add(new InstructionSequencesReplacer(sequences.CONSTANTS, sequences.OBJECT_SEQUENCES, branchTargetFinder, codeAttributeEditor, codeSimplificationObjectCounter)); // Include optimizations of instance references on classes without // constructors. peepholeOptimizations.add( new NoConstructorReferenceReplacer(codeAttributeEditor, codeSimplificationObjectCounter)); } if (codeSimplificationString) { // Peephole optimizations involving branches. peepholeOptimizations.add(new InstructionSequencesReplacer(sequences.CONSTANTS, sequences.STRING_SEQUENCES, branchTargetFinder, codeAttributeEditor, codeSimplificationStringCounter)); } if (codeSimplificationMath) { // Peephole optimizations involving math. peepholeOptimizations.add(new InstructionSequencesReplacer(sequences.CONSTANTS, sequences.MATH_SEQUENCES, branchTargetFinder, codeAttributeEditor, codeSimplificationMathCounter)); if (configuration.android) { peepholeOptimizations.add(new InstructionSequencesReplacer(sequences.CONSTANTS, sequences.MATH_ANDROID_SEQUENCES, branchTargetFinder, codeAttributeEditor, codeSimplificationAndroidMathCounter)); } } if(codeSimplificationBranch) { // Include optimization of branches to branches and returns. peepholeOptimizations.add( new GotoGotoReplacer(codeAttributeEditor, codeSimplificationBranchCounter)); peepholeOptimizations.add( new GotoReturnReplacer(codeAttributeEditor, codeSimplificationBranchCounter)); } if (fieldGeneralizationClass || methodGeneralizationClass) { // Generalize the target classes of method invocations, to // reduce the number of descriptors. peepholeOptimizations.add( new MemberReferenceGeneralizer(fieldGeneralizationClass, methodGeneralizationClass, codeAttributeEditor, fieldGeneralizationClassCounter, methodGeneralizationClassCounter)); } return peepholeOptimizations; } /** * Returns a String indicating whether the given flag is enabled or * disabled. */ private String disabled(boolean flag) { return flag ? "" : " (disabled)"; } /** * Returns a String indicating whether the given flags are enabled or * disabled. */ private String disabled(boolean flag1, boolean flag2) { return flag1 && flag2 ? "" : flag1 || flag2 ? " (partially disabled)" : " (disabled)"; } } ================================================ FILE: base/src/main/java/proguard/optimize/ParameterShrinker.java ================================================ /* * 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.VariableRemapper; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.optimize.info.*; /** * This AttributeVisitor removes unused parameters from the code of the methods * that it visits. * * @see ParameterUsageMarker * @see MethodStaticizer * @see MethodDescriptorShrinker * @author Eric Lafortune */ public class ParameterShrinker implements AttributeVisitor { private static final Logger logger = LogManager.getLogger(ParameterShrinker.class); private final MemberVisitor extraUnusedParameterMethodVisitor; private final VariableRemapper variableRemapper = new VariableRemapper(); /** * Creates a new ParameterShrinker. */ public ParameterShrinker() { this(null); } /** * Creates a new ParameterShrinker with an extra visitor. * @param extraUnusedParameterMethodVisitor an optional extra visitor for * all removed parameters. */ public ParameterShrinker(MemberVisitor extraUnusedParameterMethodVisitor) { this.extraUnusedParameterMethodVisitor = extraUnusedParameterMethodVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Get the original parameter size that was saved. int oldParameterSize = ParameterUsageMarker.getParameterSize(method); // Compute the new parameter size from the shrunk descriptor. int newParameterSize = ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz), method.getAccessFlags()); if (oldParameterSize > newParameterSize) { // Get the total size of the local variable frame. int maxLocals = codeAttribute.u2maxLocals; logger.debug("ParameterShrinker: {}.{}{}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz)); logger.debug(" Old parameter size = {}", oldParameterSize); logger.debug(" New parameter size = {}", newParameterSize); logger.debug(" Max locals = {}", maxLocals); // Create a variable map. int[] variableMap = new int[maxLocals]; // Move unused parameters right after the parameter block. int usedParameterIndex = 0; int unusedParameterIndex = newParameterSize; for (int parameterIndex = 0; parameterIndex < oldParameterSize; parameterIndex++) { // Is the variable required as a parameter? if (ParameterUsageMarker.isParameterUsed(method, parameterIndex)) { // Keep the variable as a parameter. variableMap[parameterIndex] = usedParameterIndex++; } else { logger.debug(" Deleting parameter #{}", parameterIndex); // Shift the variable to the unused parameter block, // in case it is still used as a variable. variableMap[parameterIndex] = unusedParameterIndex++; // Visit the method, if required. if (extraUnusedParameterMethodVisitor != null) { method.accept(clazz, extraUnusedParameterMethodVisitor); } } } // Fill out the remainder of the map. for (int variableIndex = oldParameterSize; variableIndex < maxLocals; variableIndex++) { variableMap[variableIndex] = variableIndex; } // Set the map. variableRemapper.setVariableMap(variableMap); // Remap the variables. variableRemapper.visitCodeAttribute(clazz, method, codeAttribute); } } } ================================================ FILE: base/src/main/java/proguard/optimize/ReverseDependencyCalculator.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.ProGuard; import proguard.classfile.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.instruction.visitor.*; import proguard.classfile.visitor.*; import proguard.optimize.info.*; import proguard.util.MultiValueMap; /** * This class serves to construct a ReverseDependencyStore through computing its depending objects; * - isCalledBy: a mapping which tells which classes refer to which other classes * - classEqualizers: a mapping which tells which classes have some method with a certain ProgramMethodOptimizationInfo * * Note that we never consider library classes as their optimizationInfo is not mutable. */ public class ReverseDependencyCalculator { private static final Logger logger = LogManager.getFormatterLogger(ReverseDependencyCalculator.class); private final ClassPool classPool; public ReverseDependencyCalculator(ClassPool classPool) { this.classPool = classPool; } /** * This function constructs the reverseDependencyStore based on the object * @return a new ReverseDependencyStore based on the class pool given to the Calculator at construction time */ public ReverseDependencyStore reverseDependencyStore() { long start = System.currentTimeMillis(); ReverseDependencyStore out = new ReverseDependencyStore(isCalledBy(), methodsByProgramMethodOptimizationInfo()); long end = System.currentTimeMillis(); logger.trace("Calculating Reverse Dependencies................ took: %6d ms", (end - start)); return out; } /** * This function constructs a map which maps a class to all other classes which refer to the key. * * The construction of this map is a two step process: * - Add all direct dependencies: These are found through a ReferencedClassVisitor * - Add the references to superclasses: These are found through a hierarchyAccept */ private MultiValueMap isCalledBy() { MultiValueMap isCalledBy = new MultiValueMap<>(); classPool.classesAccept(new AllMethodVisitor(new AddDependencies(isCalledBy))); return isCalledBy; } /** * This function constructs a map which maps a ProgramMethodOptimizationInfo to all methods have the key * object as their OptimizationInfo. * * This map is constructed through a single pass over all methods in the classPool. * */ private MultiValueMap methodsByProgramMethodOptimizationInfo() { MultiValueMap map = new MultiValueMap<>(); classPool.classesAccept(new AllMethodVisitor(new FillMethodsByProgramMethodOptimizationInfo(map))); return map; } /** * Fills the map which maps ProgramMethodOptimizationInfo to all the methods which have the key object * as their OptimizationInfo. */ private static class FillMethodsByProgramMethodOptimizationInfo implements MemberVisitor { private final MultiValueMap map; public FillMethodsByProgramMethodOptimizationInfo(MultiValueMap map) { this.map = map; } @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(programMethod); if (methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { map.put((ProgramMethodOptimizationInfo)methodOptimizationInfo,programMethod); } } @Override public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {} @Override public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {} } /** * Fills the isCalledBy map which maps a method to all LocatedMembers which can be influenced by it. */ private static class AddDependencies implements MemberVisitor { private final MultiValueMap isCalledBy; AddDependencies(MultiValueMap isCalledBy) { this.isCalledBy = isCalledBy; } @Override public void visitAnyMember(Clazz clazz, Member member) { member.accept(clazz, new AllAttributeVisitor( new AllInstructionVisitor( new CalledMemberVisitor( new Adder(new ClassMemberPair(clazz, member)))))); } /** * Adds a certain tuple to the map */ private class Adder implements MemberVisitor { private final ClassMemberPair source; Adder(ClassMemberPair source) { this.source = source; } @Override public void visitAnyMember(Clazz clazz, Member member) { } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { isCalledBy.put(programMethod, source); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/ReverseDependencyStore.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import proguard.classfile.*; import proguard.classfile.visitor.*; import proguard.optimize.info.*; import proguard.util.MultiValueMap; /** * This classes is a data class that is used to query which methods need to be reconsidered for side effects * when side effects for a certain method are derived. */ public class ReverseDependencyStore { private final MultiValueMap methodsByProgramMethodOptimizationInfo; private final MultiValueMap calledBy; /** * Constructs a ReverseDependencyStore by its contents. * * @param calledBy Maps a method to all LocatedMembers that refer to it * @param methodsByProgramMethodOptimizationInfo Maps some optimizationInfo to all Methods that use it */ public ReverseDependencyStore(MultiValueMap calledBy, MultiValueMap methodsByProgramMethodOptimizationInfo) { this.calledBy = calledBy; this.methodsByProgramMethodOptimizationInfo = methodsByProgramMethodOptimizationInfo; } /** * This MemberVisitor travels to the set of influenced methods when side effects for a certain method * are derived. * * This is a two step process: * 1) get all the methods which share the same methodOptimizationInfo * (i.e. all inherited methods share their methodOptimizationInfo throughout the whole tree) * 2) get all the methods which refer to a methods collected in step 1 * */ public class InfluencedMethodTraveller implements MemberVisitor { private final MemberVisitor memberVisitor; public InfluencedMethodTraveller(MemberVisitor memberVisitor) {this.memberVisitor = memberVisitor;} @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(programMethod); if (methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ProgramMethodOptimizationInfo info = (ProgramMethodOptimizationInfo)methodOptimizationInfo; for (Method rootMethod : methodsByProgramMethodOptimizationInfo.get(info)) { if (!calledBy.keySet().contains(rootMethod)) { continue; } for (ClassMemberPair locatedMember : calledBy.get(rootMethod)) { locatedMember.accept(memberVisitor); } } } } } } ================================================ FILE: base/src/main/java/proguard/optimize/SideEffectVisitorMarkerFactory.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import proguard.classfile.attribute.visitor.*; import proguard.classfile.instruction.visitor.*; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.*; import proguard.evaluation.value.TypedReferenceValueFactory; import proguard.optimize.evaluation.*; import proguard.optimize.info.*; /** * The Member Visitors created by this factory checks whether a particular member contains side effects. */ class SideEffectVisitorMarkerFactory implements InfluenceFixpointVisitor.MemberVisitorFactory { private final boolean optimizeConservatively; /** * @param optimizeConservatively specifies whether conservative * optimization should be applied */ public SideEffectVisitorMarkerFactory(boolean optimizeConservatively) { this.optimizeConservatively = optimizeConservatively; } // Implementations for MemberVisitorFactory public MemberVisitor createMemberVisitor(MemberVisitor influencedMethodCollector) { ReferenceTracingValueFactory referenceTracingValueFactory1 = new ReferenceTracingValueFactory(new TypedReferenceValueFactory()); PartialEvaluator partialEvaluator = new PartialEvaluator(referenceTracingValueFactory1, new ParameterTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory1)), false, referenceTracingValueFactory1); InstructionUsageMarker instructionUsageMarker = new InstructionUsageMarker(partialEvaluator, false, false); // Create the various markers. // They will be used as code attribute visitors and // instruction visitors this time. // We're currently marking read and written fields once, // outside of these iterations, for better performance, // at the cost of some effectiveness (test2209). //ReadWriteFieldMarker readWriteFieldMarker = // new ReadWriteFieldMarker(repeatTrigger); SideEffectMethodMarker sideEffectMethodMarker = new SideEffectMethodMarker(influencedMethodCollector, optimizeConservatively); ParameterEscapeMarker parameterEscapeMarker = new ParameterEscapeMarker(partialEvaluator, false, influencedMethodCollector); return new OptimizationInfoMemberFilter( // Methods with editable optimization info. new AllAttributeVisitor( new DebugAttributeVisitor("Marking fields, methods, and parameters", new MultiAttributeVisitor( partialEvaluator, parameterEscapeMarker, instructionUsageMarker, new AllInstructionVisitor( instructionUsageMarker.necessaryInstructionFilter( new MultiInstructionVisitor( // All read / write field instruction are already marked // for all code (see above), there is no need to mark them again. // If unused code is removed that accesses fields, the // respective field will be removed in the next iteration. // This is a trade-off between performance and correctness. // TODO: improve the marking for read / write fields after // performance improvements have been implemented. //readWriteFieldMarker, sideEffectMethodMarker, parameterEscapeMarker )))))) // TODO: disabled for now, see comment above. // Methods without editable optimization info, for // which we can't mark side-effects or escaping // parameters, so we can save some effort. //new AllAttributeVisitor( //new DebugAttributeVisitor("Marking fields", //new MultiAttributeVisitor( // partialEvaluator, // instructionUsageMarker, // new AllInstructionVisitor( // instructionUsageMarker.necessaryInstructionFilter( // readWriteFieldMarker))))) ); } } ================================================ FILE: base/src/main/java/proguard/optimize/TailRecursionSimplifier.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.CodeAttributeComposer; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; /** * This MemberVisitor simplifies tail recursion calls in all methods that it * visits. * * @author Eric Lafortune */ public class TailRecursionSimplifier implements AttributeVisitor, InstructionVisitor, ConstantVisitor, ExceptionInfoVisitor { private static final Logger logger = LogManager.getLogger(TailRecursionSimplifier.class); private final InstructionVisitor extraTailRecursionVisitor; private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(); private final MyRecursionChecker recursionChecker = new MyRecursionChecker(); private final StackSizeComputer stackSizeComputer = new StackSizeComputer(); private Method targetMethod; private boolean inlinedAny; /** * Creates a new TailRecursionSimplifier. */ public TailRecursionSimplifier() { this(null); } /** * Creates a new TailRecursionSimplifier with an extra visitor. * @param extraTailRecursionVisitor an optional extra visitor for all * simplified tail recursions. */ public TailRecursionSimplifier(InstructionVisitor extraTailRecursionVisitor) { this.extraTailRecursionVisitor = extraTailRecursionVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { int accessFlags = method.getAccessFlags(); if (// Only check the method if it is private, static, or final. (accessFlags & (AccessConstants.PRIVATE | AccessConstants.STATIC | AccessConstants.FINAL)) != 0 && // Only check the method if it is not synchronized, etc. (accessFlags & (AccessConstants.SYNCHRONIZED | AccessConstants.NATIVE | AccessConstants.ABSTRACT)) == 0) { // codeAttributeComposer.DEBUG = DEBUG = // clazz.getName().equals("abc/Def") && // method.getName(clazz).equals("abc"); targetMethod = method; inlinedAny = false; codeAttributeComposer.reset(); // The code may expand, due to expanding constant and variable // instructions. codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); // Copy the instructions. codeAttribute.instructionsAccept(clazz, method, this); // Update the code attribute if any code has been inlined. if (inlinedAny) { // Append a label just after the code. codeAttributeComposer.appendLabel(codeAttribute.u4codeLength); // Copy the exceptions. codeAttribute.exceptionsAccept(clazz, method, this); codeAttributeComposer.endCodeFragment(); codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute); } } } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // Copy the instruction. codeAttributeComposer.appendInstruction(offset, instruction); } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { // Is it a method invocation? switch (constantInstruction.opcode) { case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: { // Is it a recursive call? clazz.constantPoolEntryAccept(constantInstruction.constantIndex, recursionChecker); if (recursionChecker.isRecursive()) { // Is the next instruction a return? int nextOffset = offset + constantInstruction.length(offset); Instruction nextInstruction = InstructionFactory.create(codeAttribute.code, nextOffset); switch (nextInstruction.opcode) { case Instruction.OP_IRETURN: case Instruction.OP_LRETURN: case Instruction.OP_FRETURN: case Instruction.OP_DRETURN: case Instruction.OP_ARETURN: case Instruction.OP_RETURN: { // Isn't the recursive call inside a try/catch block? codeAttribute.exceptionsAccept(clazz, method, offset, recursionChecker); if (recursionChecker.isRecursive()) { // Is the stack empty after the return? stackSizeComputer.visitCodeAttribute(clazz, method, codeAttribute); if (stackSizeComputer.isReachable(nextOffset) && stackSizeComputer.getStackSizeAfter(nextOffset) == 0) { logger.debug("TailRecursionSimplifier: [{}.{}{}], inlining {}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), constantInstruction.toString(offset) ); // Append a label. codeAttributeComposer.appendLabel(offset); storeParameters(clazz, method); // Branch back to the start of the method. int gotoOffset = offset + 1; codeAttributeComposer.appendInstruction(gotoOffset, new BranchInstruction(Instruction.OP_GOTO, -gotoOffset)); // The original return instruction will be // removed elsewhere, if possible. // Remember that the code has changed. inlinedAny = true; if (extraTailRecursionVisitor != null) { extraTailRecursionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); } // The invocation itself is no longer necessary. return; } } } } } break; } } // Copy the instruction. codeAttributeComposer.appendInstruction(offset, constantInstruction); } // Implementations for ExceptionInfoVisitor. public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) { codeAttributeComposer.appendException(new ExceptionInfo(exceptionInfo.u2startPC, exceptionInfo.u2endPC, exceptionInfo.u2handlerPC, exceptionInfo.u2catchType)); } /** * This ConstantVisitor and ExceptionInfoVisitor returns whether a method * invocation can be treated as tail-recursive. */ private class MyRecursionChecker implements ConstantVisitor, ExceptionInfoVisitor { private boolean recursive; /** * Returns whether the method invocation can be treated as * tail-recursive. */ public boolean isRecursive() { return recursive; } // Implementations for ConstantVisitor. public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant methodrefConstant) { recursive = targetMethod.equals(methodrefConstant.referencedMethod); } // Implementations for ExceptionInfoVisitor. public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) { recursive = false; } } // Small utility methods. /** * Appends instructions to pop the parameters for the given method, storing * them in new local variables. */ private void storeParameters(Clazz clazz, Method method) { String descriptor = method.getDescriptor(clazz); boolean isStatic = (method.getAccessFlags() & AccessConstants.STATIC) != 0; // Count the number of parameters, taking into account their categories. int parameterSize = ClassUtil.internalMethodParameterSize(descriptor); int parameterOffset = isStatic ? 0 : 1; // Store the parameter types. String[] parameterTypes = new String[parameterSize]; InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); for (int parameterIndex = 0; parameterIndex < parameterSize; parameterIndex++) { String parameterType = internalTypeEnumeration.nextType(); parameterTypes[parameterIndex] = parameterType; if (ClassUtil.internalTypeSize(parameterType) == 2) { parameterIndex++; } } codeAttributeComposer.beginCodeFragment(parameterSize + 1); // Go over the parameter types backward, storing the stack entries // in their corresponding variables. for (int parameterIndex = parameterSize-1; parameterIndex >= 0; parameterIndex--) { String parameterType = parameterTypes[parameterIndex]; if (parameterType != null) { byte opcode; switch (parameterType.charAt(0)) { case TypeConstants.BOOLEAN: case TypeConstants.BYTE: case TypeConstants.CHAR: case TypeConstants.SHORT: case TypeConstants.INT: opcode = Instruction.OP_ISTORE; break; case TypeConstants.LONG: opcode = Instruction.OP_LSTORE; break; case TypeConstants.FLOAT: opcode = Instruction.OP_FSTORE; break; case TypeConstants.DOUBLE: opcode = Instruction.OP_DSTORE; break; default: opcode = Instruction.OP_ASTORE; break; } codeAttributeComposer.appendInstruction(parameterSize-parameterIndex-1, new VariableInstruction(opcode, parameterOffset + parameterIndex)); } } // Put the 'this' reference in variable 0. if (!isStatic) { codeAttributeComposer.appendInstruction(parameterSize, new VariableInstruction(Instruction.OP_ASTORE, 0)); } codeAttributeComposer.endCodeFragment(); } } ================================================ FILE: base/src/main/java/proguard/optimize/TimedClassPoolVisitor.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.ClassPool; import proguard.classfile.visitor.*; import proguard.optimize.info.ParameterEscapedMarker; /** * A simple class pool visitor that will output timing information. */ public class TimedClassPoolVisitor implements ClassPoolVisitor { private static final Logger logger = LogManager.getFormatterLogger(TimedClassPoolVisitor.class); private final String message; private final ClassPoolVisitor classPoolVisitor; public TimedClassPoolVisitor(String message, ClassVisitor classVisitor) { this(message, new AllClassVisitor(classVisitor)); } public TimedClassPoolVisitor(String message, ClassPoolVisitor classPoolVisitor) { this.message = message; this.classPoolVisitor = classPoolVisitor; } // Implementations for ClassPoolVisitor. public void visitClassPool(ClassPool classPool) { long start = System.currentTimeMillis(); classPool.accept(classPoolVisitor); long end = System.currentTimeMillis(); logger.trace("%s %s took: %6d ms", message, getPadding(message.length(), 48), (end - start)); } // Private helper methods private String getPadding(int pos, int size) { StringBuilder sb = new StringBuilder(); for (int i = pos; i < size; i++) { sb.append('.'); } return sb.toString(); } } ================================================ FILE: base/src/main/java/proguard/optimize/WriteOnlyFieldFilter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.info.ReadWriteFieldMarker; /** * This MemberVisitor delegates its visits to program fields to * other given MemberVisitor instances, but only when the visited * field has been marked as write-only. * * @see ReadWriteFieldMarker * @author Eric Lafortune */ public class WriteOnlyFieldFilter implements MemberVisitor { private final MemberVisitor writeOnlyFieldVisitor; /** * Creates a new WriteOnlyFieldFilter. * @param writeOnlyFieldVisitor the MemberVisitor to which * visits to write-only fields will be delegated. */ public WriteOnlyFieldFilter(MemberVisitor writeOnlyFieldVisitor) { this.writeOnlyFieldVisitor = writeOnlyFieldVisitor; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { if (ReadWriteFieldMarker.isWritten(programField) && !ReadWriteFieldMarker.isRead(programField)) { writeOnlyFieldVisitor.visitProgramField(programClass, programField); } } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/EvaluationShrinker.java ================================================ /* * 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.evaluation; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.RefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.*; import proguard.evaluation.*; import proguard.evaluation.value.*; import proguard.optimize.info.ParameterUsageMarker; import java.io.PrintWriter; import java.io.StringWriter; /** * This AttributeVisitor shrinks the code attributes that it visits, based * on partial evaluation. * * @author Eric Lafortune */ public class EvaluationShrinker implements AttributeVisitor, ExceptionInfoVisitor { private static final Logger logger = LogManager.getLogger(EvaluationShrinker.class); // Useful short sequences of simple instructions (LSB first). private static final int UNSUPPORTED = -1; private static final int NOP = Instruction.OP_NOP & 0xff; private static final int POP = Instruction.OP_POP & 0xff; private static final int POP2 = Instruction.OP_POP2 & 0xff; private static final int DUP = Instruction.OP_DUP & 0xff; private static final int DUP_X1 = Instruction.OP_DUP_X1 & 0xff; private static final int DUP_X2 = Instruction.OP_DUP_X2 & 0xff; private static final int DUP2 = Instruction.OP_DUP2 & 0xff; private static final int DUP2_X1 = Instruction.OP_DUP2_X1 & 0xff; private static final int DUP2_X2 = Instruction.OP_DUP2_X2 & 0xff; private static final int SWAP = Instruction.OP_SWAP & 0xff; private static final int MOV_X2 = DUP_X2 | (POP << 8); private static final int MOV2_X1 = DUP2_X1 | (POP2 << 8); private static final int MOV2_X2 = DUP2_X2 | (POP2 << 8); private static final int POP_X1 = SWAP | (POP << 8); private static final int POP_X2 = DUP2_X1 | (POP2 << 8) | (POP << 16); private static final int POP_X3 = UNSUPPORTED; private static final int POP2_X1 = DUP_X2 | (POP << 8) | (POP2 << 16); private static final int POP2_X2 = DUP2_X2 | (POP2 << 8) | (POP2 << 16); private static final int POP3 = POP2 | (POP << 8); private static final int POP4 = POP2 | (POP2 << 8); private static final int POP_DUP = POP | (DUP << 8); private static final int POP_DUP_X1 = POP | (DUP_X1 << 8); private static final int POP_SWAP = POP | (SWAP << 8); private static final int POP_SWAP_POP = POP | (SWAP << 8) | (POP << 16); private static final int POP_SWAP_POP_DUP = POP | (SWAP << 8) | (POP << 16) | (DUP << 24); private static final int POP2_SWAP_POP = POP2 | (SWAP << 8) | (POP << 16); private static final int SWAP_DUP_X1 = SWAP | (DUP_X1 << 8); private static final int SWAP_DUP_X1_POP3 = SWAP | (DUP_X1 << 8) | (POP2 << 16) | (POP << 24); private static final int SWAP_DUP2_X1_POP3 = SWAP | (DUP2_X1 << 8) | (POP2 << 16) | (POP << 24); private static final int SWAP_DUP_X1_SWAP = SWAP | (DUP_X1 << 8) | (SWAP << 16); private static final int SWAP_POP_DUP = SWAP | (POP << 8) | (DUP << 16); private static final int SWAP_POP_DUP_X1 = SWAP | (POP << 8) | (DUP_X1 << 16); private static final int DUP_X2_POP = DUP_X2 | (POP << 8); private static final int DUP_X2_POP2 = DUP_X2 | (POP2 << 8); private static final int DUP_X2_POP3_DUP = DUP_X2 | (POP << 8) | (POP2 << 16) | (DUP << 24); private static final int DUP2_X1_POP = DUP2_X1 | (POP << 8); private static final int DUP2_X1_POP3_DUP = DUP2_X1 | (POP2 << 8) | (POP << 16) | (DUP << 24); private static final int DUP2_X1_POP3_DUP_X1 = DUP2_X1 | (POP2 << 8) | (POP << 16) | (DUP_X1 << 24); private static final int DUP2_X1_POP3_DUP2 = DUP2_X1 | (POP2 << 8) | (POP << 16) | (DUP2 << 24); private static final int DUP2_X2_POP3 = DUP2_X2 | (POP2 << 8) | (POP << 16); private static final int DUP2_X2_SWAP_POP = DUP2_X2 | (SWAP << 8) | (POP << 16); private final InstructionUsageMarker instructionUsageMarker; private final boolean runInstructionUsageMarker; private final InstructionVisitor extraDeletedInstructionVisitor; private final InstructionVisitor extraAddedInstructionVisitor; private final MyStaticInvocationFixer staticInvocationFixer = new MyStaticInvocationFixer(); private final MyBackwardBranchFixer backwardBranchFixer = new MyBackwardBranchFixer(); private final MyNonReturningSubroutineFixer nonReturningSubroutineFixer = new MyNonReturningSubroutineFixer(); private final MyStackConsistencyFixer stackConsistencyFixer = new MyStackConsistencyFixer(); private final MyInstructionDeleter instructionDeleter = new MyInstructionDeleter(); private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(false, true); /** * Creates a new EvaluationShrinker. */ public EvaluationShrinker() { this(new PartialEvaluator(), true, false, null, null); } /** * Creates a new EvaluationShrinker. * @param partialEvaluator the partial evaluator that will * analyze the code. * @param runPartialEvaluator specifies whether the partial * evaluator should be run for each * method, or if some other class is * already doing this. * @param optimizeConservatively specifies whether conservative * optimization should be applied * @param extraDeletedInstructionVisitor an optional extra visitor for all * deleted instructions. * @param extraAddedInstructionVisitor an optional extra visitor for all * added instructions. */ public EvaluationShrinker(PartialEvaluator partialEvaluator, boolean runPartialEvaluator, boolean optimizeConservatively, InstructionVisitor extraDeletedInstructionVisitor, InstructionVisitor extraAddedInstructionVisitor) { this(new InstructionUsageMarker(partialEvaluator, runPartialEvaluator, optimizeConservatively), true, extraDeletedInstructionVisitor, extraAddedInstructionVisitor); } /** * Creates a new EvaluationShrinker. * @param instructionUsageMarker the instruction usage marker that * will analyze the code. * @param runInstructionUsageMarker specifies whether the usage * marker should be run for each * method, or if some other class is * already doing this. * @param extraDeletedInstructionVisitor an optional extra visitor for all * deleted instructions. * @param extraAddedInstructionVisitor an optional extra visitor for all * added instructions. */ public EvaluationShrinker(InstructionUsageMarker instructionUsageMarker, boolean runInstructionUsageMarker, InstructionVisitor extraDeletedInstructionVisitor, InstructionVisitor extraAddedInstructionVisitor) { this.instructionUsageMarker = instructionUsageMarker; this.runInstructionUsageMarker = runInstructionUsageMarker; this.extraDeletedInstructionVisitor = extraDeletedInstructionVisitor; this.extraAddedInstructionVisitor = extraAddedInstructionVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // DEBUG = DEBUG_RESULTS = // clazz.getName().equals("abc/Def") && // method.getName(clazz).equals("abc"); // TODO: Remove this when the evaluation shrinker has stabilized. // Catch any unexpected exceptions from the actual visiting method. try { // Process the code. visitCodeAttribute0(clazz, method, codeAttribute); } catch (RuntimeException ex) { logger.error("Unexpected error while shrinking instructions after partial evaluation:"); logger.error(" Class = [{}]", clazz.getName()); logger.error(" Method = [{}{}]", method.getName(clazz), method.getDescriptor(clazz)); logger.error(" Exception = [{}] ({})", ex.getClass().getName(), ex.getMessage()); ex.printStackTrace(); logger.error("Not optimizing this method"); logger.debug("{}", () -> { StringWriter sw = new StringWriter(); method.accept(clazz, new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); if (logger.getLevel().isLessSpecificThan(Level.DEBUG)) { throw ex; } } } public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) { logger.debug("EvaluationShrinker [{}.{}]", clazz.getName(), method.getName(clazz)+method.getDescriptor(clazz)); // Analyze the method. if (runInstructionUsageMarker) { instructionUsageMarker.visitCodeAttribute(clazz, method, codeAttribute); } int codeLength = codeAttribute.u4codeLength; // Reset the code changes. codeAttributeEditor.reset(codeLength); // Replace virtual invocations by static invocations, where necessary. logger.debug("Static invocation fixing:"); codeAttribute.instructionsAccept(clazz, method, instructionUsageMarker.necessaryInstructionFilter(true, staticInvocationFixer)); // Replace traced but unnecessary backward branches by infinite loops. // The virtual machine's verification step is not smart enough to see // the code isn't reachable, and may complain otherwise. // Any clearly unreachable code will still be removed elsewhere. logger.debug("Backward branch fixing:"); codeAttribute.instructionsAccept(clazz, method, instructionUsageMarker.tracedInstructionFilter(true, instructionUsageMarker.necessaryInstructionFilter(false, backwardBranchFixer))); // Insert infinite loops after jumps to subroutines that don't return. // The virtual machine's verification step is not smart enough to see // the code isn't reachable, and may complain otherwise. logger.debug("Non-returning subroutine fixing:"); codeAttribute.instructionsAccept(clazz, method, instructionUsageMarker.necessaryInstructionFilter(true, nonReturningSubroutineFixer)); // Locally fix instructions, in order to keep the stack consistent. logger.debug("Stack consistency fixing:"); codeAttribute.instructionsAccept(clazz, method, instructionUsageMarker.tracedInstructionFilter(true, stackConsistencyFixer)); // Delete all instructions that are not used. logger.debug("Deleting unused instructions"); codeAttribute.instructionsAccept(clazz, method, instructionUsageMarker.necessaryInstructionFilter(false, instructionDeleter)); logger.debug("Simplification results:"); if (logger.getLevel().isLessSpecificThan(Level.DEBUG)) { int offset = 0; do { Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); logger.debug("{}{}", (instructionUsageMarker.isInstructionNecessary(offset) ? " + " : instructionUsageMarker.isExtraPushPopInstructionNecessary(offset) ? " ~ " : " - "), instruction.toString(offset) ); if (instructionUsageMarker.isTraced(offset)) { InstructionOffsetValue branchTargets = instructionUsageMarker.branchTargets(offset); if (branchTargets != null) { logger.debug(" has overall been branching to {}", branchTargets); } boolean deleted = codeAttributeEditor.deleted[offset]; if (instructionUsageMarker.isInstructionNecessary(offset) && deleted) { logger.debug(" is deleted"); } Instruction preInsertion = codeAttributeEditor.preInsertions[offset]; if (preInsertion != null) { logger.debug(" is preceded by: {}", preInsertion); } Instruction replacement = codeAttributeEditor.replacements[offset]; if (replacement != null) { logger.debug(" is replaced by: {}", replacement); } Instruction postInsertion = codeAttributeEditor.postInsertions[offset]; if (postInsertion != null) { logger.debug(" is followed by: {}", postInsertion); } } offset += instruction.length(offset); } while (offset < codeLength); } // Clear exception handlers that are not necessary. codeAttribute.exceptionsAccept(clazz, method, this); // Apply all accumulated changes to the code. codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } /** * This MemberVisitor converts virtual method invocations into static * method invocations if the 'this' parameter isn't used. */ private class MyStaticInvocationFixer implements InstructionVisitor, ConstantVisitor, MemberVisitor { private int invocationOffset; private ConstantInstruction invocationInstruction; // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKEINTERFACE: this.invocationOffset = offset; this.invocationInstruction = constantInstruction; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; } } // Implementations for ConstantVisitor. public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { refConstant.referencedMemberAccept(this); } // Implementations for MemberVisitor. public void visitAnyMember(Clazz clazz, Member member) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Make the method invocation static, if possible. if ((programMethod.getAccessFlags() & AccessConstants.STATIC) == 0 && !ParameterUsageMarker.isParameterUsed(programMethod, 0)) { replaceByStaticInvocation(programClass, invocationOffset, invocationInstruction); } } } /** * This InstructionVisitor replaces all backward branches by * infinite loops. */ private class MyBackwardBranchFixer implements InstructionVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // Is it a traced but unmarked backward branch, without an unmarked // straddling forward branch? Note that this is still a heuristic. if (isAllSmallerThanOrEqual(instructionUsageMarker.branchTargets(offset), offset) && !isAnyUnnecessaryInstructionBranchingOver(lastNecessaryInstructionOffset(offset), offset)) { replaceByInfiniteLoop(clazz, offset); logger.debug(" Setting infinite loop instead of {}", instruction.toString(offset)); } } /** * Returns whether all of the given instruction offsets (at least one) * are smaller than or equal to the given offset. */ private boolean isAllSmallerThanOrEqual(InstructionOffsetValue instructionOffsets, int instructionOffset) { if (instructionOffsets != null) { // Loop over all instruction offsets. int branchCount = instructionOffsets.instructionOffsetCount(); if (branchCount > 0) { for (int branchIndex = 0; branchIndex < branchCount; branchIndex++) { // Is the offset larger than the reference offset? if (instructionOffsets.instructionOffset(branchIndex) > instructionOffset) { return false; } } return true; } } return false; } /** * Returns the highest offset of an instruction that has been marked as * necessary, before the given offset. */ private int lastNecessaryInstructionOffset(int instructionOffset) { for (int offset = instructionOffset-1; offset >= 0; offset--) { if (instructionUsageMarker.isInstructionNecessary(instructionOffset)) { return offset; } } return 0; } } /** * This InstructionVisitor appends infinite loops after all visited * non-returning subroutine invocations. */ private class MyNonReturningSubroutineFixer implements InstructionVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { // Is it a necessary subroutine invocation? if (branchInstruction.canonicalOpcode() == Instruction.OP_JSR) { int nextOffset = offset + branchInstruction.length(offset); if (!instructionUsageMarker.isInstructionNecessary(nextOffset)) { replaceByInfiniteLoop(clazz, nextOffset); logger.debug(" Adding infinite loop at [{}] after {}", nextOffset, branchInstruction.toString(offset)); } } } } /** * This InstructionVisitor fixes instructions locally, popping any unused * produced stack entries after marked instructions, and popping produced * stack entries and pushing missing stack entries instead of unmarked * instructions. */ private class MyStackConsistencyFixer implements InstructionVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // Has the instruction been marked? if (instructionUsageMarker.isInstructionNecessary(offset)) { // Check all stack entries that are popped. // Unusual case: an exception handler with an exception that is // no longer consumed as a method parameter. // Typical case: a freshly marked variable initialization that // requires some value on the stack. // In practice, it can be at most one of those at once. int popCount = instruction.stackPopCount(clazz); if (popCount > 0) { TracedStack tracedStack = instructionUsageMarker.getStackBefore(offset); int stackSize = tracedStack.size(); // We represent entries that need to be popped or pushed as // a bitmask. In theory, the pop mask could overflow, but // the case is very rare to begin with. int requiredPopMask = 0; int requiredPushMask = 0; for (int stackIndex = stackSize - popCount; stackIndex < stackSize; stackIndex++) { requiredPopMask <<= 1; requiredPushMask <<= 1; boolean stackEntryUnwantedBefore = instructionUsageMarker.isStackEntryUnwantedBefore(offset, stackIndex); boolean stackEntryPresentBefore = instructionUsageMarker.isStackEntryPresentBefore(offset, stackIndex); if (stackEntryUnwantedBefore) { if (stackEntryPresentBefore) { // Remember to pop it. requiredPopMask |= 1; } } else { if (!stackEntryPresentBefore) { // Remember to push some value. requiredPushMask |= 1; } } } // Pop some unnecessary stack entries. if (requiredPopMask > 0) { logger.debug(" Popping 0x{} before marked consumer {}", Integer.toHexString(requiredPopMask), instruction.toString(offset)); insertInstructions(offset, false, true, instruction, simpleInstructions(complexPop(requiredPopMask))); } // Push some necessary stack entries. if (requiredPushMask > 0) { Value value = tracedStack.getTop(0); if (requiredPushMask != (value.isCategory2() ? 3 : 1)) { throw new IllegalArgumentException("Unsupported stack size increment ["+requiredPushMask+"] at ["+offset+"]"); } logger.debug(" Pushing {} before marked consumer {}", value.computationalType(), instruction.toString(offset)); insertPushInstructions(offset, false, true, value.computationalType()); } } // Check all stack entries that are pushed. // Typical case: a return value that wasn't really required and // that should be popped. int pushCount = instruction.stackPushCount(clazz); if (pushCount > 0) { TracedStack tracedStack = instructionUsageMarker.getStackAfter(offset); int stackSize = tracedStack.size(); int requiredPopCount = 0; for (int stackIndex = stackSize - pushCount; stackIndex < stackSize; stackIndex++) { // Is the stack entry required by consumers? if (!instructionUsageMarker.isStackEntryNecessaryAfter(offset, stackIndex)) { // Remember to pop it. requiredPopCount++; } } // Pop the unnecessary stack entries. if (requiredPopCount > 0) { logger.debug(" Popping {} entries after marked producer {}", requiredPopCount, instruction.toString(offset)); insertPopInstructions(offset, false, false, requiredPopCount); } } } else if (instructionUsageMarker.isExtraPushPopInstructionNecessary(offset)) { // Check all stack entries that would be popped. // Typical case: a stack value that is required elsewhere and // that still has to be popped. int popCount = instruction.stackPopCount(clazz); if (popCount > 0) { TracedStack tracedStack = instructionUsageMarker.getStackBefore(offset); int stackSize = tracedStack.size(); int expectedPopCount = 0; for (int stackIndex = stackSize - popCount; stackIndex < stackSize; stackIndex++) { // Is this stack entry pushed by any producer // (because it is required by other consumers)? if (instructionUsageMarker.isStackEntryPresentBefore(offset, stackIndex)) { // Remember to pop it. expectedPopCount++; } } // Pop the unnecessary stack entries. if (expectedPopCount > 0) { logger.debug(" Popping {} entries instead of unmarked consumer {}", expectedPopCount, instruction.toString(offset)); insertPopInstructions(offset, true, false, expectedPopCount); } } // Check all stack entries that would be pushed. // Typical case: a corresponding stack entry is pushed // elsewhere so it still has to be pushed here. int pushCount = instruction.stackPushCount(clazz); if (pushCount > 0) { TracedStack tracedStack = instructionUsageMarker.getStackAfter(offset); int stackSize = tracedStack.size(); int expectedPushCount = 0; for (int stackIndex = stackSize - pushCount; stackIndex < stackSize; stackIndex++) { // Is the stack entry required by consumers? if (instructionUsageMarker.isStackEntryNecessaryAfter(offset, stackIndex)) { // Remember to push it. expectedPushCount++; } } // Push some necessary stack entries. if (expectedPushCount > 0) { logger.debug(" Pushing type {} entry instead of unmarked producer {}", tracedStack.getTop(0).computationalType(), instruction.toString(offset)); insertPushInstructions(offset, true, false, tracedStack.getTop(0).computationalType()); } } } } public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { if (instructionUsageMarker.isInstructionNecessary(offset) && isDupOrSwap(simpleInstruction)) { int topBefore = instructionUsageMarker.getStackBefore(offset).size() - 1; int topAfter = instructionUsageMarker.getStackAfter(offset).size() - 1; byte oldOpcode = simpleInstruction.opcode; // Simplify the dup/swap instruction if possible. int newOpcodes = fixDupSwap(offset, oldOpcode, topBefore, topAfter); replaceBySimpleInstructions(offset, simpleInstruction, newOpcodes); } else { visitAnyInstruction(clazz, method, codeAttribute, offset, simpleInstruction); } } public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { if (instructionUsageMarker.isInstructionNecessary(offset)) { if (branchInstruction.stackPopCount(clazz) > 0 && !instructionUsageMarker.isStackEntryPresentBefore(offset, instructionUsageMarker.getStackBefore(offset).size() - 1)) { // Replace the branch instruction by a simple goto. Instruction replacementInstruction = new BranchInstruction(Instruction.OP_GOTO, branchInstruction.branchOffset); codeAttributeEditor.replaceInstruction(offset, replacementInstruction); logger.debug(" Replacing branch instruction {} by {}", branchInstruction.toString(offset), replacementInstruction.toString()); } } else { visitAnyInstruction(clazz, method, codeAttribute, offset, branchInstruction); } } public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction) { if (instructionUsageMarker.isInstructionNecessary(offset)) { if (switchInstruction.stackPopCount(clazz) > 0 && !instructionUsageMarker.isStackEntryPresentBefore(offset, instructionUsageMarker.getStackBefore(offset).size() - 1)) { // Replace the switch instruction by a simple goto. Instruction replacementInstruction = new BranchInstruction(Instruction.OP_GOTO, switchInstruction.defaultOffset); codeAttributeEditor.replaceInstruction(offset, replacementInstruction); logger.debug(" Replacing switch instruction {} by {}", switchInstruction.toString(offset), replacementInstruction.toString()); } } else { visitAnyInstruction(clazz, method, codeAttribute, offset, switchInstruction); } } /** * Returns whether the given instruction is a dup or swap instruction * (dup, dup_x1, dup_x2, dup2, dup2_x1, dup2_x2, swap). */ private boolean isDupOrSwap(Instruction instruction) { return instruction.opcode >= Instruction.OP_DUP && instruction.opcode <= Instruction.OP_SWAP; } /** * Returns a dup/swap opcode that is corrected for the stack entries * that are present before the instruction and necessary after the * instruction. The returned integer opcode may contain multiple byte * opcodes (least significant byte first). * @param instructionOffset the offset of the dup/swap instruction. * @param dupSwapOpcode the original dup/swap opcode. * @param topBefore the index of the top stack entry before * the instruction (counting from the bottom). * @param topAfter the index of the top stack entry after * the instruction (counting from the bottom). * @return the corrected opcode. */ private int fixDupSwap(int instructionOffset, byte dupSwapOpcode, int topBefore, int topAfter) { switch (dupSwapOpcode) { case Instruction.OP_DUP: return fixedDup (instructionOffset, topBefore, topAfter); case Instruction.OP_DUP_X1: return fixedDup_x1 (instructionOffset, topBefore, topAfter); case Instruction.OP_DUP_X2: return fixedDup_x2 (instructionOffset, topBefore, topAfter); case Instruction.OP_DUP2: return fixedDup2 (instructionOffset, topBefore, topAfter); case Instruction.OP_DUP2_X1: return fixedDup2_x1(instructionOffset, topBefore, topAfter); case Instruction.OP_DUP2_X2: return fixedDup2_x2(instructionOffset, topBefore, topAfter); case Instruction.OP_SWAP: return fixedSwap (instructionOffset, topBefore, topAfter); default: throw new IllegalArgumentException("Not a dup/swap opcode ["+dupSwapOpcode+"]"); } } private int fixedDup(int instructionOffset, int topBefore, int topAfter) { boolean stackEntryPresent0 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore); boolean stackEntryNecessary0 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter); boolean stackEntryNecessary1 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 1); // Figure out which stack entries should be moved, // copied, or removed. return stackEntryNecessary0 ? stackEntryNecessary1 ? DUP : // ...O -> ...OO NOP : // ...O -> ...O stackEntryNecessary1 ? NOP : // ...O -> ...O stackEntryPresent0 ? POP : // ...O -> ... NOP; // ... -> ... } private int fixedDup_x1(int instructionOffset, int topBefore, int topAfter) { boolean stackEntryPresent0 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore); boolean stackEntryPresent1 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore - 1); boolean stackEntryNecessary0 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter); boolean stackEntryNecessary1 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 1); boolean stackEntryNecessary2 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 2); // Figure out which stack entries should be moved, // copied, or removed. return stackEntryNecessary1 ? stackEntryNecessary2 ? stackEntryNecessary0 ? DUP_X1 : // ...XO -> ...OXO SWAP : // ...XO -> ...OX // !stackEntryNecessary2 stackEntryNecessary0 ? NOP : // ...XO -> ...XO stackEntryPresent0 ? POP : // ...XO -> ...X NOP : // ...X -> ...X stackEntryPresent1 ? stackEntryNecessary2 ? stackEntryNecessary0 ? SWAP_POP_DUP : // ...XO -> ...OO POP_X1 : // ...XO -> ...O // !stackEntryNecessary2 stackEntryNecessary0 ? POP_X1 : // ...XO -> ...O stackEntryPresent0 ? POP2 : // ...XO -> ... POP : // ...X -> ... // !stackEntryPresent1 stackEntryNecessary2 ? stackEntryNecessary0 ? DUP : // ...O -> ...OO NOP : // ...O -> ...O // !stackEntryNecessary2 stackEntryNecessary0 ? NOP : // ...O -> ...O stackEntryPresent0 ? POP : // ...O -> ... NOP; // ... -> ... } private int fixedDup_x2(int instructionOffset, int topBefore, int topAfter) { boolean stackEntryPresent0 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore); boolean stackEntryPresent1 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore - 1); boolean stackEntryPresent2 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore - 2); boolean stackEntryNecessary0 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter); boolean stackEntryNecessary1 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 1); boolean stackEntryNecessary2 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 2); boolean stackEntryNecessary3 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 3); // Figure out which stack entries should be moved, // copied, or removed. return stackEntryNecessary1 ? stackEntryNecessary2 ? stackEntryNecessary3 ? stackEntryNecessary0 ? DUP_X2 : // ...XYO -> ...OXYO MOV_X2 : // ...XYO -> ...OXY // !stackEntryNecessary3 stackEntryNecessary0 ? NOP : // ...XYO -> ...XYO stackEntryPresent0 ? POP : // ...XYO -> ...XY NOP : // ...XY -> ...XY stackEntryPresent2 ? stackEntryNecessary3 ? stackEntryNecessary0 ? DUP2_X1_POP3_DUP_X1 : // ...XYO -> ...OYO SWAP_DUP2_X1_POP3 : // ...XYO -> ...OY // !stackEntryNecessary3 stackEntryNecessary0 ? POP_X2 : // ...XYO -> ...YO stackEntryPresent0 ? POP_SWAP_POP : // ...XYO -> ...Y POP_X1 : // ...XY -> ...Y // !stackEntryPresent2 stackEntryNecessary3 ? stackEntryNecessary0 ? DUP_X1 : // ...YO -> ...OYO SWAP : // ...YO -> ...OY // !stackEntryNecessary3 stackEntryNecessary0 ? NOP : // ...YO -> ...YO stackEntryPresent0 ? POP : // ...YO -> ...Y NOP : // ...Y -> ...Y stackEntryPresent1 ? stackEntryNecessary2 ? stackEntryNecessary3 ? stackEntryNecessary0 ? SWAP_POP_DUP_X1 : // ...XYO -> ...OXO DUP_X2_POP2 : // ...XYO -> ...OX // !stackEntryNecessary3 stackEntryNecessary0 ? POP_X1 : // ...XYO -> ...XO stackEntryPresent0 ? POP2 : // ...XYO -> ...X POP : // ...XY -> ...X stackEntryPresent2 ? stackEntryNecessary3 ? stackEntryNecessary0 ? DUP_X2_POP3_DUP : // ...XYO -> ...OO POP2_X1 : // ...XYO -> ...O // !stackEntryNecessary3 stackEntryNecessary0 ? POP2_X1 : // ...XYO -> ...O stackEntryPresent0 ? POP3 : // ...XYO -> ... POP2 : // ...XY -> ... // !stackEntryPresent2 stackEntryNecessary3 ? stackEntryNecessary0 ? SWAP_POP_DUP : // ...YO -> ...OO POP_X1 : // ...YO -> ...O // !stackEntryNecessary3 stackEntryNecessary0 ? POP_X1 : // ...YO -> ...O stackEntryPresent0 ? POP2 : // ...YO -> ... POP : // ...Y -> ... // !stackEntryPresent1 stackEntryNecessary2 ? stackEntryNecessary3 ? stackEntryNecessary0 ? DUP_X1 : // ...XO -> ...OXO SWAP : // ...XO -> ...OX // !stackEntryNecessary3 stackEntryNecessary0 ? NOP : // ...XO -> ...XO stackEntryPresent0 ? POP : // ...XO -> ...X NOP : // ...X -> ...X stackEntryPresent2 ? stackEntryNecessary3 ? stackEntryNecessary0 ? SWAP_POP_DUP : // ...XO -> ...OO POP_X1 : // ...XO -> ...O // !stackEntryNecessary3 stackEntryNecessary0 ? POP_X1 : // ...XO -> ...O stackEntryPresent0 ? POP2 : // ...XO -> ... POP : // ...X -> ... // !stackEntryPresent2 stackEntryNecessary3 ? stackEntryNecessary0 ? DUP : // ...O -> ...OO NOP : // ...O -> ...O // !stackEntryNecessary3 stackEntryNecessary0 ? NOP : // ...O -> ...O stackEntryPresent0 ? POP : // ...O -> ... NOP; // ... -> ... } private int fixedDup2(int instructionOffset, int topBefore, int topAfter) { boolean stackEntryPresent0 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore); boolean stackEntryPresent1 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore - 1); boolean stackEntryNecessary0 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter); boolean stackEntryNecessary1 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 1); boolean stackEntryNecessary2 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 2); boolean stackEntryNecessary3 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 3); // Figure out which stack entries should be moved, // copied, or removed. return stackEntryNecessary3 ? stackEntryNecessary2 ? stackEntryNecessary1 ? stackEntryNecessary0 ? DUP2 : // ...AB -> ...ABAB SWAP_DUP_X1 : // ...AB -> ...ABA // !stackEntryNecessary1 stackEntryNecessary0 ? DUP : // ...AB -> ...ABB NOP : // ...AB -> ...AB // !stackEntryNecessary2 stackEntryNecessary1 ? stackEntryNecessary0 ? SWAP_DUP_X1_SWAP : // ...AB -> ...AAB stackEntryPresent0 ? POP_DUP : // ...AB -> ...AA DUP : // ...A -> ...AA // !stackEntryNecessary1 stackEntryNecessary0 ? NOP : // ...AB -> ...AB stackEntryPresent0 ? POP : // ...AB -> ...A NOP : // ...A -> ...A // !stackEntryNecessary3 stackEntryNecessary2 ? stackEntryNecessary1 ? stackEntryNecessary0 ? DUP_X1 : // ...AB -> ...BAB SWAP : // ...AB -> ...BA stackEntryPresent1 ? stackEntryNecessary0 ? SWAP_POP_DUP : // ...AB -> ...BB POP_X1 : // ...AB -> ...B // !stackEntryPresent1 stackEntryNecessary0 ? DUP : // ...B -> ...BB NOP : // ...B -> ...B // !stackEntryNecessary2 stackEntryNecessary1 ? stackEntryNecessary0 ? NOP : // ...AB -> ...AB stackEntryPresent0 ? POP : // ...AB -> ...A NOP : // ...A -> ...A stackEntryPresent1 ? stackEntryNecessary0 ? POP_X1 : // ...AB -> ...B stackEntryPresent0 ? POP2 : // ...AB -> ... POP : // ...A -> ... // !stackEntryPresent1 stackEntryNecessary0 ? NOP : // ...B -> ...B stackEntryPresent0 ? POP : // ...B -> ... NOP; // ... -> ... } private int fixedDup2_x1(int instructionOffset, int topBefore, int topAfter) { boolean stackEntryPresent0 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore); boolean stackEntryPresent1 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore - 1); boolean stackEntryPresent2 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore - 2); boolean stackEntryNecessary0 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter); boolean stackEntryNecessary1 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 1); boolean stackEntryNecessary2 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 2); boolean stackEntryNecessary3 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 3); boolean stackEntryNecessary4 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 4); // Figure out which stack entries should be moved, // copied, or removed. return stackEntryNecessary4 ? stackEntryNecessary3 ? stackEntryNecessary2 ? stackEntryNecessary1 ? stackEntryNecessary0 ? DUP2_X1 : // ...XAB -> ...ABXAB DUP2_X1_POP : // ...XAB -> ...ABXA // !stackEntryNecessary1 stackEntryNecessary0 ? UNSUPPORTED : // ...XAB -> ...ABXB MOV2_X1 : // ...XAB -> ...ABX stackEntryPresent2 ? stackEntryNecessary1 ? stackEntryNecessary0 ? DUP2_X1_POP3_DUP2 : // ...XAB -> ...ABAB UNSUPPORTED : // ...XAB -> ...ABA // !stackEntryNecessary1 stackEntryNecessary0 ? DUP2_X1_POP3_DUP : // ...XAB -> ...ABB POP_X2 : // ...XAB -> ...AB // !stackEntryNecessary2 stackEntryNecessary1 ? stackEntryNecessary0 ? DUP2 : // ...AB -> ...ABAB SWAP_DUP_X1 : // ...AB -> ...ABA // !stackEntryNecessary1 stackEntryNecessary0 ? DUP : // ...AB -> ...ABB NOP : // ...AB -> ...AB // !stackEntryNecessary3 stackEntryNecessary2 ? stackEntryNecessary1 ? stackEntryNecessary0 ? UNSUPPORTED : // ...XAB -> ...AXAB stackEntryPresent0 ? POP_DUP_X1 : // ...XAB -> ...AXA DUP_X1 : // ...XA -> ...AXA // !stackEntryNecessary1 stackEntryNecessary0 ? UNSUPPORTED : // ...XAB -> ...AXB stackEntryPresent0 ? POP_SWAP : // ...XAB -> ...AX SWAP : // ...XA -> ...AX stackEntryPresent2 ? stackEntryNecessary1 ? stackEntryNecessary0 ? UNSUPPORTED : // ...XAB -> ...AAB stackEntryPresent0 ? POP_SWAP_POP_DUP : // ...XAB -> ...AA SWAP_POP_DUP : // ...XA -> ...AA // !stackEntryNecessary1 stackEntryNecessary0 ? POP_X2 : // ...XAB -> ...AB stackEntryPresent0 ? POP_SWAP_POP : // ...XAB -> ...A POP_X1 : // ...XA -> ...A // !stackEntryNecessary2 stackEntryNecessary1 ? stackEntryNecessary0 ? SWAP_DUP_X1_SWAP : // ...AB -> ...AAB stackEntryPresent0 ? POP_DUP : // ...AB -> ...AA DUP : // ...A -> ...AA // !stackEntryNecessary1 stackEntryNecessary0 ? NOP : // ...AB -> ...AB stackEntryPresent0 ? POP : // ...AB -> ...A NOP : // ...A -> ...A // !stackEntryNecessary4 stackEntryNecessary3 ? stackEntryNecessary2 ? stackEntryNecessary1 ? stackEntryNecessary0 ? DUP_X2 : // ...XAB -> ...BXAB DUP_X2_POP : // ...XAB -> ...BXA stackEntryPresent1 ? stackEntryNecessary0 ? SWAP_POP_DUP_X1 : // ...XAB -> ...BXB DUP_X2_POP2 : // ...XAB -> ...BX // !stackEntryNecessary1 stackEntryNecessary0 ? POP_X2 : // ...XB -> ...BXB SWAP : // ...XB -> ...BX stackEntryPresent2 ? stackEntryNecessary1 ? stackEntryNecessary0 ? DUP2_X1_POP3_DUP_X1 : // ...XAB -> ...BAB SWAP_DUP_X1_POP3 : // ...XAB -> ...BA stackEntryPresent1 ? stackEntryNecessary0 ? DUP_X2_POP3_DUP : // ...XAB -> ...BB POP2_X1 : // ...XAB -> ...B // !stackEntryNecessary1 stackEntryNecessary0 ? SWAP_POP_DUP : // ...XB -> ...BB POP_X1 : // ...XB -> ...B // !stackEntryNecessary2 stackEntryNecessary1 ? stackEntryNecessary0 ? DUP_X1 : // ...AB -> ...BAB SWAP : // ...AB -> ...BA stackEntryPresent1 ? stackEntryNecessary0 ? SWAP_POP_DUP : // ...AB -> ...BB POP_X1 : // ...AB -> ...B // !stackEntryNecessary1 stackEntryNecessary0 ? DUP : // ...B -> ...BB NOP : // ...B -> ...B // !stackEntryNecessary3 stackEntryNecessary2 ? stackEntryNecessary1 ? stackEntryNecessary0 ? NOP : // ...XAB -> ...XAB stackEntryPresent0 ? POP : // ...XAB -> ...XA NOP : // ...XA -> ...XA stackEntryPresent1 ? stackEntryNecessary0 ? POP_X1 : // ...XAB -> ...XB stackEntryPresent0 ? POP2 : // ...XAB -> ...X POP : // ...XA -> ...X // !stackEntryNecessary1 stackEntryNecessary0 ? NOP : // ...XB -> ...XB stackEntryPresent0 ? POP : // ...XB -> ...X NOP : // ...X -> ...X stackEntryPresent2 ? stackEntryNecessary1 ? stackEntryNecessary0 ? POP_X2 : // ...XAB -> ...AB stackEntryPresent0 ? POP_SWAP_POP : // ...XAB -> ...A POP_X1 : // ...XA -> ...A stackEntryPresent1 ? stackEntryNecessary0 ? POP2_X1 : // ...XAB -> ...B stackEntryPresent0 ? POP3 : // ...XAB -> ... POP2 : // ...XA -> ... // !stackEntryNecessary1 stackEntryNecessary0 ? POP_X1 : // ...XB -> ...B stackEntryPresent0 ? POP2 : // ...XB -> ... POP : // ...X -> ... // !stackEntryNecessary2 stackEntryNecessary1 ? stackEntryNecessary0 ? NOP : // ...AB -> ...AB stackEntryPresent0 ? POP : // ...AB -> ...A NOP : // ...A -> ...A stackEntryPresent1 ? stackEntryNecessary0 ? POP_X1 : // ...AB -> ...B stackEntryPresent0 ? POP2 : // ...AB -> ... POP : // ...A -> ... // !stackEntryNecessary1 stackEntryNecessary0 ? NOP : // ...B -> ...B stackEntryPresent0 ? POP : // ...B -> ... NOP; // ... -> ... } private int fixedDup2_x2(int instructionOffset, int topBefore, int topAfter) { // We're currently assuming the value to be duplicated // is a long or a double, taking up two slots, or at // least consistent. boolean stackEntriesPresent01 = instructionUsageMarker.isStackEntriesPresentBefore(instructionOffset, topBefore, topBefore - 1); boolean stackEntryPresent2 = instructionUsageMarker.isStackEntryPresentBefore( instructionOffset, topBefore - 2); boolean stackEntryPresent3 = instructionUsageMarker.isStackEntryPresentBefore( instructionOffset, topBefore - 3); boolean stackEntriesNecessary01 = instructionUsageMarker.isStackEntriesNecessaryAfter(instructionOffset, topAfter, topAfter - 1); boolean stackEntryNecessary2 = instructionUsageMarker.isStackEntryNecessaryAfter( instructionOffset, topAfter - 2); boolean stackEntryNecessary3 = instructionUsageMarker.isStackEntryNecessaryAfter( instructionOffset, topAfter - 3); boolean stackEntriesNecessary45 = instructionUsageMarker.isStackEntriesNecessaryAfter(instructionOffset, topAfter - 4, topAfter - 5); // Figure out which stack entries should be moved, // copied, or removed. return stackEntryNecessary2 ? stackEntryNecessary3 ? stackEntriesNecessary45 ? stackEntriesNecessary01 ? DUP2_X2 : // ...XYAB -> ...ABXYAB MOV2_X2 : // ...XYAB -> ...ABXY // !stackEntriesNecessary45 stackEntriesNecessary01 ? NOP : // ...XYAB -> ...XYAB stackEntriesPresent01 ? POP2 : // ...XYAB -> ...XY NOP : // ...XY -> ...XY stackEntryPresent3 ? stackEntriesNecessary45 ? stackEntriesNecessary01 ? UNSUPPORTED : // ...XYAB -> ...ABYAB DUP2_X2_SWAP_POP : // ...XYAB -> ...ABY // !stackEntriesNecessary45 stackEntriesNecessary01 ? POP_X3 : // ...XYAB -> ...YAB stackEntriesPresent01 ? POP2_SWAP_POP : // ...XYAB -> ...Y POP_X1 : // ...XY -> ...Y // !stackEntryPresent3 stackEntriesNecessary45 ? stackEntriesNecessary01 ? DUP2_X1 : // ...YAB -> ...ABYAB MOV2_X1 : // ...YAB -> ...ABY // !stackEntriesNecessary45 stackEntriesNecessary01 ? NOP : // ...YAB -> ...YAB stackEntriesPresent01 ? POP2 : // ...YAB -> ...Y NOP : // ...Y -> ...Y stackEntryPresent2 ? stackEntryNecessary3 ? stackEntriesNecessary45 ? stackEntriesNecessary01 ? UNSUPPORTED : // ...XYAB -> ...ABXAB DUP2_X2_POP3 : // ...XYAB -> ...ABX // !stackEntriesNecessary45 stackEntriesNecessary01 ? POP_X2 : // ...XYAB -> ...XAB stackEntriesPresent01 ? POP3 : // ...XYAB -> ...X POP : // ...XY -> ...X stackEntryPresent3 ? stackEntriesNecessary45 ? stackEntriesNecessary01 ? UNSUPPORTED : // ...XYAB -> ...ABAB POP2_X2 : // ...XYAB -> ...AB // !stackEntriesNecessary45 stackEntriesNecessary01 ? POP2_X2 : // ...XYAB -> ...AB stackEntriesPresent01 ? POP4 : // ...XYAB -> ... POP2 : // ...XY -> ... // !stackEntryPresent3 stackEntriesNecessary45 ? stackEntriesNecessary01 ? DUP2_X1_POP3_DUP2 : // ...YAB -> ...ABAB POP_X2 : // ...YAB -> ...AB // !stackEntriesNecessary45 stackEntriesNecessary01 ? POP_X2 : // ...YAB -> ...AB stackEntriesPresent01 ? POP3 : // ...YAB -> ... POP : // ...Y -> ... // !stackEntryPresent2 stackEntryNecessary3 ? stackEntriesNecessary45 ? stackEntriesNecessary01 ? DUP2_X1 : // ...XAB -> ...ABXAB MOV2_X1 : // ...XAB -> ...ABX // !stackEntriesNecessary45 stackEntriesNecessary01 ? NOP : // ...XAB -> ...XAB stackEntriesPresent01 ? POP2 : // ...XAB -> ...X NOP : // ...X -> ...X stackEntryPresent3 ? stackEntriesNecessary45 ? stackEntriesNecessary01 ? DUP2_X1_POP3_DUP2 : // ...XAB -> ...ABAB POP_X2 : // ...XAB -> ...AB // !stackEntriesNecessary45 stackEntriesNecessary01 ? POP_X2 : // ...XAB -> ...AB stackEntriesPresent01 ? POP3 : // ...XAB -> ... POP : // ...X -> ... // !stackEntryPresent3 stackEntriesNecessary45 ? stackEntriesNecessary01 ? DUP2 : // ...AB -> ...ABAB NOP : // ...AB -> ...AB // !stackEntriesNecessary45 stackEntriesNecessary01 ? NOP : // ...AB -> ...AB stackEntriesPresent01 ? POP2 : // ...AB -> ... NOP; // ... -> ... } private int fixedSwap(int instructionOffset, int topBefore, int topAfter) { boolean stackEntryPresent0 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore); boolean stackEntryPresent1 = instructionUsageMarker.isStackEntryPresentBefore(instructionOffset, topBefore - 1); boolean stackEntryNecessary0 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter); boolean stackEntryNecessary1 = instructionUsageMarker.isStackEntryNecessaryAfter(instructionOffset, topAfter - 1); // Figure out which stack entries should be moved // or removed. return stackEntryNecessary0 ? stackEntryNecessary1 ? SWAP : // ...AB -> ...BA stackEntryPresent0 ? POP : // ...AB -> ...A NOP : // ...A -> ...A stackEntryPresent1 ? POP_X1 : // ...AB -> ...B NOP; // ...B -> ...B } /** * Returns sequence of dup/swap opcodes that pop the given mask. * @param popMask the mask of stack entries that needs to be popped, * where the least significant bit corresponds to the * top of the stack. * @return the sequence of simple opcodes. */ private int complexPop(int popMask) { switch (popMask) { case 0x01: return POP; case 0x03: return POP2; case 0x07: return POP3; case 0x0f: return POP4; case 0x02: return POP_X1; case 0x04: return POP_X2; default: throw new UnsupportedOperationException("Can't remove complex pattern of entries from stack [0x"+Integer.toHexString(popMask)+"]"); } } } /** * This InstructionVisitor deletes all visited instructions. */ private class MyInstructionDeleter implements InstructionVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { codeAttributeEditor.deleteInstruction(offset); // We're allowing edits on deleted instructions. //codeAttributeEditor.insertBeforeInstruction(offset, (Instruction)null); //codeAttributeEditor.replaceInstruction(offset, (Instruction)null); //codeAttributeEditor.insertAfterInstruction(offset, (Instruction)null); // Visit the instruction, if required. if (extraDeletedInstructionVisitor != null) { instruction.accept(clazz, method, codeAttribute, offset, extraDeletedInstructionVisitor); } } } // Implementations for ExceptionInfoVisitor. public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) { // Is the catch handler necessary? if (!instructionUsageMarker.isTraced(exceptionInfo.u2handlerPC)) { // Make the code block empty, so the code editor can remove it. exceptionInfo.u2endPC = exceptionInfo.u2startPC; } } // Small utility methods. /** * Returns whether any traced but unnecessary instruction between the two * given offsets is branching over the second given offset. */ private boolean isAnyUnnecessaryInstructionBranchingOver(int instructionOffset1, int instructionOffset2) { for (int offset = instructionOffset1; offset < instructionOffset2; offset++) { // Is it a traced but unmarked straddling branch? if (instructionUsageMarker.isTraced(offset) && !instructionUsageMarker.isInstructionNecessary(offset) && isAnyLargerThan(instructionUsageMarker.branchTargets(offset), instructionOffset2)) { return true; } } return false; } /** * Returns whether any of the given instruction offsets (at least one) * is larger than the given offset. */ private boolean isAnyLargerThan(InstructionOffsetValue instructionOffsets, int instructionOffset) { if (instructionOffsets != null) { // Loop over all instruction offsets. int branchCount = instructionOffsets.instructionOffsetCount(); if (branchCount > 0) { for (int branchIndex = 0; branchIndex < branchCount; branchIndex++) { // Is the offset larger than the reference offset? if (instructionOffsets.instructionOffset(branchIndex) > instructionOffset) { return true; } } } } return false; } /** * Pushes a specified type of stack entry before or at the given offset. * The instruction is marked as necessary. */ private void insertPushInstructions(int offset, boolean replace, boolean before, int computationalType) { // We can edit an instruction without marking it. //markInstruction(offset); // Create a simple push instrucion. Instruction replacementInstruction = new SimpleInstruction(pushOpcode(computationalType)); logger.debug(": {}", replacementInstruction.toString(offset)); // Replace or insert the push instruction. insertInstruction(offset, replace, before, replacementInstruction); } /** * Returns the opcode of a push instruction corresponding to the given * computational type. * @param computationalType the computational type to be pushed on the stack. */ private byte pushOpcode(int computationalType) { switch (computationalType) { case Value.TYPE_INTEGER: return Instruction.OP_ICONST_0; case Value.TYPE_LONG: return Instruction.OP_LCONST_0; case Value.TYPE_FLOAT: return Instruction.OP_FCONST_0; case Value.TYPE_DOUBLE: return Instruction.OP_DCONST_0; case Value.TYPE_REFERENCE: case Value.TYPE_INSTRUCTION_OFFSET: return Instruction.OP_ACONST_NULL; } throw new IllegalArgumentException("No push opcode for computational type ["+computationalType+"]"); } /** * Pops the given number of stack entries at or after the given offset. * The instructions are marked as necessary. */ private void insertPopInstructions(int offset, boolean replace, boolean before, int popCount) { // We can edit an instruction without marking it. //markInstruction(offset); switch (popCount) { case 1: { // Replace or insert a single pop instruction. Instruction popInstruction = new SimpleInstruction(Instruction.OP_POP); insertInstruction(offset, replace, before, popInstruction); break; } case 2: { // Replace or insert a single pop2 instruction. Instruction popInstruction = new SimpleInstruction(Instruction.OP_POP2); insertInstruction(offset, replace, before, popInstruction); break; } default: { // Replace or insert the specified number of pop instructions. Instruction[] popInstructions = new Instruction[popCount / 2 + popCount % 2]; Instruction popInstruction = new SimpleInstruction(Instruction.OP_POP2); for (int index = 0; index < popCount / 2; index++) { popInstructions[index] = popInstruction; } if (popCount % 2 == 1) { popInstruction = new SimpleInstruction(Instruction.OP_POP); popInstructions[popCount / 2] = popInstruction; } insertInstructions(offset, replace, before, popInstruction, popInstructions); break; } } } /** * Inserts or replaces the given instruction at the given offset. */ private void insertInstruction(int offset, boolean replace, boolean before, Instruction instruction) { if (replace) { codeAttributeEditor.replaceInstruction(offset, instruction); if (extraAddedInstructionVisitor != null && !instructionUsageMarker.isInstructionNecessary(offset)) { instruction.accept(null, null, null, offset, extraAddedInstructionVisitor); } } else { if (before) { codeAttributeEditor.insertBeforeInstruction(offset, instruction); } else { codeAttributeEditor.insertAfterInstruction(offset, instruction); } if (extraAddedInstructionVisitor != null) { instruction.accept(null, null, null, offset, extraAddedInstructionVisitor); } } } /** * Inserts or replaces the given instruction at the given offset. */ private void insertInstructions(int offset, boolean replace, boolean before, Instruction instruction, Instruction[] instructions) { if (replace) { codeAttributeEditor.replaceInstruction(offset, instructions); if (extraAddedInstructionVisitor != null) { if (!instructionUsageMarker.isInstructionNecessary(offset)) { instruction.accept(null, null, null, offset, extraAddedInstructionVisitor); } for (int index = 1; index < instructions.length; index++) { instructions[index].accept(null, null, null, offset, extraAddedInstructionVisitor); } } } else { if (before) { codeAttributeEditor.insertBeforeInstruction(offset, instructions); } else { codeAttributeEditor.insertAfterInstruction(offset, instructions); } for (int index = 0; index < instructions.length; index++) { if (extraAddedInstructionVisitor != null) { instructions[index].accept(null, null, null, offset, extraAddedInstructionVisitor); } } } } /** * Replaces the given invocation instruction by a static invocation. */ private void replaceByStaticInvocation(Clazz clazz, int offset, ConstantInstruction constantInstruction) { // Remember the replacement instruction. Instruction replacementInstruction = new ConstantInstruction(Instruction.OP_INVOKESTATIC, constantInstruction.constantIndex); logger.debug(" Replacing by static invocation {} -> {}", constantInstruction.toString(offset), replacementInstruction.toString()); codeAttributeEditor.replaceInstruction(offset, replacementInstruction); } /** * Replaces the specified instruction by an infinite loop. */ private void replaceByInfiniteLoop(Clazz clazz, int offset) { logger.debug(" Inserting infinite loop at [{}]", offset); // We can edit an instruction without marking it. //markInstruction(offset); // Replace the instruction by an infinite loop. Instruction replacementInstruction = new BranchInstruction(Instruction.OP_GOTO, 0); codeAttributeEditor.replaceInstruction(offset, replacementInstruction); } /** * Replaces the given instruction by one or more instructions with the * given simple opcodes. */ private void replaceBySimpleInstructions(int offset, Instruction oldInstruction, int newOpcodes) { // Did we find a suitable (extended) opcode? if (newOpcodes == UNSUPPORTED) { // We can't easily emulate some constructs. throw new UnsupportedOperationException("Can't handle " + oldInstruction.toString() + " instruction at [" + offset + "]"); } // Did we get a single replacement opcode? if ((newOpcodes & ~0xff) == 0) { byte newOpcode = (byte)newOpcodes; if (newOpcode == Instruction.OP_NOP) { // Delete the instruction. codeAttributeEditor.deleteInstruction(offset); if (extraDeletedInstructionVisitor != null) { extraDeletedInstructionVisitor.visitSimpleInstruction(null, null, null, offset, null); } logger.debug(" Deleting marked instruction {}", oldInstruction.toString(offset)); } else if (newOpcode == oldInstruction.opcode) { // Leave the instruction unchanged. codeAttributeEditor.undeleteInstruction(offset); logger.debug(" Marking unchanged instruction {}", oldInstruction.toString(offset)); } else { // Replace the instruction. Instruction replacementInstruction = new SimpleInstruction(newOpcode); codeAttributeEditor.replaceInstruction(offset, replacementInstruction); logger.debug(" Replacing instruction {} by {}", oldInstruction.toString(offset), replacementInstruction.toString() ); } } else { logger.debug(" Replacing instruction {} by", oldInstruction.toString(offset)); // Replace the instruction. Instruction[] replacementInstructions = simpleInstructions(newOpcodes); codeAttributeEditor.replaceInstruction(offset, replacementInstructions); } } /** * Returns the simple instructions with the given opcodes (LSB first). */ private Instruction[] simpleInstructions(int opcodes) { // Did we find a suitable (extended) opcode? if (opcodes == UNSUPPORTED) { // We can't easily emulate some constructs. throw new UnsupportedOperationException("Can't perform complex stack manipulation"); } // Collect the replacement instructions. Instruction[] instructions = new Instruction[4]; int count = 0; while (opcodes != 0) { Instruction replacementInstruction = new SimpleInstruction((byte)opcodes); instructions[count++] = replacementInstruction; logger.debug(" {}", replacementInstruction.toString()); opcodes >>>= 8; } // Create a properly sized array. if (count < 4) { Instruction[] newInstructions = new Instruction[count]; System.arraycopy(instructions, 0, newInstructions, 0, count); instructions = newInstructions; } return instructions; } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/EvaluationSimplifier.java ================================================ /* * 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.evaluation; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.ClassPrinter; import proguard.evaluation.*; import proguard.evaluation.value.*; import proguard.optimize.info.SideEffectInstructionChecker; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; /** * This AttributeVisitor simplifies the code attributes that it visits, based * on partial evaluation. * * @author Eric Lafortune */ public class EvaluationSimplifier implements AttributeVisitor, InstructionVisitor { private static final int POS_ZERO_FLOAT_BITS = Float.floatToIntBits(0.0f); private static final long POS_ZERO_DOUBLE_BITS = Double.doubleToLongBits(0.0); private static final Logger logger = LogManager.getLogger(EvaluationSimplifier.class); private final boolean predictNullPointerExceptions; private final InstructionVisitor extraInstructionVisitor; private final PartialEvaluator partialEvaluator; private final SideEffectInstructionChecker sideEffectInstructionChecker; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true); /** * Creates a new EvaluationSimplifier. * * @param predictNullPointerExceptions specifies whether instructions that will always result in a * NullPointerException should be replaced with an explicit NullPointerException */ public EvaluationSimplifier(boolean predictNullPointerExceptions) { this(new PartialEvaluator(), null, predictNullPointerExceptions); } /** * Creates a new EvaluationSimplifier. * @param partialEvaluator the partial evaluator that will * execute the code and provide * information about the results. * @param extraInstructionVisitor an optional extra visitor for all * simplified instructions. * @param predictNullPointerExceptions specifies whether instructions that will always result in a * NullPointerException should be replaced with an explicit NullPointerException */ public EvaluationSimplifier(PartialEvaluator partialEvaluator, InstructionVisitor extraInstructionVisitor, boolean predictNullPointerExceptions) { this.predictNullPointerExceptions = predictNullPointerExceptions; this.partialEvaluator = partialEvaluator; this.extraInstructionVisitor = extraInstructionVisitor; this.sideEffectInstructionChecker = new SideEffectInstructionChecker(true, true, predictNullPointerExceptions); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // DEBUG = // clazz.getName().equals("abc/Def") && // method.getName(clazz).equals("abc"); // TODO: Remove this when the evaluation simplifier has stabilized. // Catch any unexpected exceptions from the actual visiting method. try { // Process the code. visitCodeAttribute0(clazz, method, codeAttribute); } catch (RuntimeException ex) { logger.error("Unexpected error while simplifying instructions after partial evaluation:"); logger.error(" Class = [{}]", clazz.getName()); logger.error(" Method = [{}{}]", method.getName(clazz), method.getDescriptor(clazz)); logger.error(" Exception = [{}] ({})", ex.getClass().getName(), ex.getMessage()); logger.error("Not optimizing this method"); logger.debug("{}", () -> { StringWriter sw = new StringWriter(); method.accept(clazz, new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); if (logger.getLevel().isLessSpecificThan(Level.DEBUG)) { throw ex; } } } public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) { logger.debug("EvaluationSimplifier [{}.{}{}]", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz)); // Evaluate the method. partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); int codeLength = codeAttribute.u4codeLength; // Reset the code changes. codeAttributeEditor.reset(codeLength); // Replace any instructions that can be simplified. for (int offset = 0; offset < codeLength; offset++) { if (partialEvaluator.isTraced(offset)) { Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); instruction.accept(clazz, method, codeAttribute, offset, this); } } // Apply all accumulated changes to the code. codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } // Implementations for InstructionVisitor. public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { switch (simpleInstruction.opcode) { case Instruction.OP_IDIV: case Instruction.OP_IREM: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceIntegerPushInstruction(clazz, offset, simpleInstruction); } else if (isDivisionByZero(offset, Value.TYPE_INTEGER)) { // In case we detected a certain division by zero, and OPTIMIZE.CONSERVATIVELY // is enabled, replace the instruction by the explicit exception. replaceByException(clazz, offset, simpleInstruction, "java/lang/ArithmeticException"); } break; case Instruction.OP_LDIV: case Instruction.OP_LREM: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceLongPushInstruction(clazz, offset, simpleInstruction); } else if (isDivisionByZero(offset, Value.TYPE_LONG)) { // In case we detected a certain division by zero, and OPTIMIZE.CONSERVATIVELY // is enabled, replace the instruction by the explicit exception. replaceByException(clazz, offset, simpleInstruction, "java/lang/ArithmeticException"); } break; case Instruction.OP_FDIV: case Instruction.OP_FREM: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceFloatPushInstruction(clazz, offset, simpleInstruction); } else if (isDivisionByZero(offset, Value.TYPE_FLOAT)) { // In case we detected a certain division by zero, and OPTIMIZE.CONSERVATIVELY // is enabled, replace the instruction by the explicit exception. replaceByException(clazz, offset, simpleInstruction, "java/lang/ArithmeticException"); } break; case Instruction.OP_DDIV: case Instruction.OP_DREM: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceDoublePushInstruction(clazz, offset, simpleInstruction); } else if (isDivisionByZero(offset, Value.TYPE_DOUBLE)) { // In case we detected a certain division by zero, and OPTIMIZE.CONSERVATIVELY // is enabled, replace the instruction by the explicit exception. replaceByException(clazz, offset, simpleInstruction, "java/lang/ArithmeticException"); } break; case Instruction.OP_IALOAD: case Instruction.OP_BALOAD: case Instruction.OP_CALOAD: case Instruction.OP_SALOAD: case Instruction.OP_ARRAYLENGTH: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceIntegerPushInstruction(clazz, offset, simpleInstruction); } else if (isNullReference(offset, simpleInstruction.stackPopCount(clazz) - 1)) { // In case we detected a certain access to a null array, and OPTIMIZE.CONSERVATIVELY // is enabled, replace the instruction by the explicit exception. replaceByException(clazz, offset, simpleInstruction, "java/lang/NullPointerException"); } break; case Instruction.OP_IADD: case Instruction.OP_ISUB: case Instruction.OP_IMUL: case Instruction.OP_INEG: case Instruction.OP_ISHL: case Instruction.OP_ISHR: case Instruction.OP_IUSHR: case Instruction.OP_IAND: case Instruction.OP_IOR: case Instruction.OP_IXOR: case Instruction.OP_L2I: case Instruction.OP_F2I: case Instruction.OP_D2I: case Instruction.OP_I2B: case Instruction.OP_I2C: case Instruction.OP_I2S: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceIntegerPushInstruction(clazz, offset, simpleInstruction); } break; case Instruction.OP_LALOAD: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceLongPushInstruction(clazz, offset, simpleInstruction); } else if (isNullReference(offset, simpleInstruction.stackPopCount(clazz) - 1)) { // In case we detected a certain access to a null array, and OPTIMIZE.CONSERVATIVELY // is enabled, replace the instruction by the explicit exception. replaceByException(clazz, offset, simpleInstruction, "java/lang/NullPointerException"); } break; case Instruction.OP_LADD: case Instruction.OP_LSUB: case Instruction.OP_LMUL: case Instruction.OP_LNEG: case Instruction.OP_LSHL: case Instruction.OP_LSHR: case Instruction.OP_LUSHR: case Instruction.OP_LAND: case Instruction.OP_LOR: case Instruction.OP_LXOR: case Instruction.OP_I2L: case Instruction.OP_F2L: case Instruction.OP_D2L: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceLongPushInstruction(clazz, offset, simpleInstruction); } break; case Instruction.OP_FALOAD: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceFloatPushInstruction(clazz, offset, simpleInstruction); } else if (isNullReference(offset, simpleInstruction.stackPopCount(clazz) - 1)) { // In case we detected a certain access to a null array, and OPTIMIZE.CONSERVATIVELY // is enabled, replace the instruction by the explicit exception. replaceByException(clazz, offset, simpleInstruction, "java/lang/NullPointerException"); } break; case Instruction.OP_FADD: case Instruction.OP_FSUB: case Instruction.OP_FMUL: case Instruction.OP_FNEG: case Instruction.OP_I2F: case Instruction.OP_L2F: case Instruction.OP_D2F: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceFloatPushInstruction(clazz, offset, simpleInstruction); } break; case Instruction.OP_DALOAD: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceDoublePushInstruction(clazz, offset, simpleInstruction); } else if (isNullReference(offset, simpleInstruction.stackPopCount(clazz) - 1)) { // In case we detected a certain access to a null array, and OPTIMIZE.CONSERVATIVELY // is enabled, replace the instruction by the explicit exception. replaceByException(clazz, offset, simpleInstruction, "java/lang/NullPointerException"); } break; case Instruction.OP_DADD: case Instruction.OP_DSUB: case Instruction.OP_DMUL: case Instruction.OP_DNEG: case Instruction.OP_I2D: case Instruction.OP_L2D: case Instruction.OP_F2D: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceDoublePushInstruction(clazz, offset, simpleInstruction); } break; case Instruction.OP_AALOAD: if (!sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, simpleInstruction)) { replaceReferencePushInstruction(clazz, offset, simpleInstruction); } else if (isNullReference(offset, simpleInstruction.stackPopCount(clazz) - 1)) { // In case we detected a certain access to a null array, and OPTIMIZE.CONSERVATIVELY // is enabled, replace the instruction by the explicit exception. replaceByException(clazz, offset, simpleInstruction, "java/lang/NullPointerException"); } break; case Instruction.OP_IASTORE: case Instruction.OP_BASTORE: case Instruction.OP_CASTORE: case Instruction.OP_SASTORE: case Instruction.OP_LASTORE: case Instruction.OP_FASTORE: case Instruction.OP_DASTORE: case Instruction.OP_AASTORE: if (predictNullPointerExceptions && isNullReference(offset, simpleInstruction.stackPopCount(clazz) - 1)) { // In case we detected access to an array which we are absolutely certain is a null reference, // and we are predicting null pointer exceptions, replace the instruction with an explicit // NullPointerException. replaceByException(clazz, offset, simpleInstruction, "java/lang/NullPointerException"); } break; } } public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { int variableIndex = variableInstruction.variableIndex; switch (variableInstruction.opcode) { case Instruction.OP_ILOAD: case Instruction.OP_ILOAD_0: case Instruction.OP_ILOAD_1: case Instruction.OP_ILOAD_2: case Instruction.OP_ILOAD_3: replaceIntegerPushInstruction(clazz, offset, variableInstruction, variableIndex); break; case Instruction.OP_LLOAD: case Instruction.OP_LLOAD_0: case Instruction.OP_LLOAD_1: case Instruction.OP_LLOAD_2: case Instruction.OP_LLOAD_3: replaceLongPushInstruction(clazz, offset, variableInstruction, variableIndex); break; case Instruction.OP_FLOAD: case Instruction.OP_FLOAD_0: case Instruction.OP_FLOAD_1: case Instruction.OP_FLOAD_2: case Instruction.OP_FLOAD_3: replaceFloatPushInstruction(clazz, offset, variableInstruction, variableIndex); break; case Instruction.OP_DLOAD: case Instruction.OP_DLOAD_0: case Instruction.OP_DLOAD_1: case Instruction.OP_DLOAD_2: case Instruction.OP_DLOAD_3: replaceDoublePushInstruction(clazz, offset, variableInstruction, variableIndex); break; case Instruction.OP_ALOAD: case Instruction.OP_ALOAD_0: case Instruction.OP_ALOAD_1: case Instruction.OP_ALOAD_2: case Instruction.OP_ALOAD_3: replaceReferencePushInstruction(clazz, offset, variableInstruction); break; case Instruction.OP_ASTORE: case Instruction.OP_ASTORE_0: case Instruction.OP_ASTORE_1: case Instruction.OP_ASTORE_2: case Instruction.OP_ASTORE_3: deleteReferencePopInstruction(clazz, offset, variableInstruction); break; case Instruction.OP_RET: replaceBranchInstruction(clazz, offset, variableInstruction); break; } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKEINTERFACE: if (predictNullPointerExceptions && isNullReference(offset, constantInstruction.stackPopCount(clazz) - 1)) { // In case a method is invoked on a null reference // replace the instruction with an explicit NullPointerException. // This is mainly needed to counter obfuscated code that might // use exceptions to change the control flow. This is especially // problematic if it happens with methods that are explicitly marked // as having no side-effect (e.g. String#length()) as they might get // removed otherwise. replaceByException(clazz, offset, constantInstruction, "java/lang/NullPointerException"); break; } // intended fallthrough case Instruction.OP_GETSTATIC: case Instruction.OP_GETFIELD: case Instruction.OP_INVOKESTATIC: if (constantInstruction.stackPushCount(clazz) > 0 && !sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, constantInstruction)) { replaceAnyPushInstruction(clazz, offset, constantInstruction); } break; case Instruction.OP_CHECKCAST: replaceReferencePushInstruction(clazz, offset, constantInstruction); break; } } public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { switch (branchInstruction.opcode) { case Instruction.OP_GOTO: case Instruction.OP_GOTO_W: // Don't replace unconditional branches. break; case Instruction.OP_JSR: case Instruction.OP_JSR_W: replaceJsrInstruction(clazz, offset, branchInstruction); break; default: replaceBranchInstruction(clazz, offset, branchInstruction); break; } } public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction) { // First try to simplify it to a simple branch. replaceBranchInstruction(clazz, offset, tableSwitchInstruction); // Otherwise try to simplify simple enum switches. if (!codeAttributeEditor.isModified(offset)) { replaceSimpleEnumSwitchInstruction(clazz, codeAttribute, offset, tableSwitchInstruction); // Otherwise make sure all branch targets are valid. if (!codeAttributeEditor.isModified(offset)) { cleanUpSwitchInstruction(clazz, offset, tableSwitchInstruction); trimSwitchInstruction(clazz, offset, tableSwitchInstruction); } } } public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction) { // First try to simplify it to a simple branch. replaceBranchInstruction(clazz, offset, lookUpSwitchInstruction); // Otherwise try to simplify simple enum switches. if (!codeAttributeEditor.isModified(offset)) { replaceSimpleEnumSwitchInstruction(clazz, codeAttribute, offset, lookUpSwitchInstruction); // Otherwise make sure all branch targets are valid. if (!codeAttributeEditor.isModified(offset)) { cleanUpSwitchInstruction(clazz, offset, lookUpSwitchInstruction); trimSwitchInstruction(clazz, offset, lookUpSwitchInstruction); } } } // Small utility methods. /** * Replaces the push instruction at the given offset by a simpler push * instruction, if possible. */ private void replaceAnyPushInstruction(Clazz clazz, int offset, Instruction instruction) { Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0); if (pushedValue.isParticular()) { switch (pushedValue.computationalType()) { case Value.TYPE_INTEGER: replaceIntegerPushInstruction(clazz, offset, instruction); break; case Value.TYPE_LONG: replaceLongPushInstruction(clazz, offset, instruction); break; case Value.TYPE_FLOAT: replaceFloatPushInstruction(clazz, offset, instruction); break; case Value.TYPE_DOUBLE: replaceDoublePushInstruction(clazz, offset, instruction); break; case Value.TYPE_REFERENCE: replaceReferencePushInstruction(clazz, offset, instruction); break; } } } /** * Replaces the integer pushing instruction at the given offset by a simpler * push instruction, if possible. */ private void replaceIntegerPushInstruction(Clazz clazz, int offset, Instruction instruction) { replaceIntegerPushInstruction(clazz, offset, instruction, partialEvaluator.getVariablesBefore(offset).size()); } /** * Replaces the integer pushing instruction at the given offset by a simpler * push instruction, if possible. */ private void replaceIntegerPushInstruction(Clazz clazz, int offset, Instruction instruction, int maxVariableIndex) { Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0); if (pushedValue.isParticular()) { // Push a constant instead. int value = pushedValue.integerValue().value(); if ((short)value == value) { replaceConstantPushInstruction(clazz, offset, instruction, Instruction.OP_SIPUSH, value); } else { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); Instruction replacementInstruction = new ConstantInstruction(Instruction.OP_LDC, constantPoolEditor.addIntegerConstant(value)); replaceInstruction(clazz, offset, instruction, replacementInstruction); } } else if (pushedValue.isSpecific()) { // Load an equivalent lower-numbered variable instead, if any. TracedVariables variables = partialEvaluator.getVariablesBefore(offset); for (int variableIndex = 0; variableIndex < maxVariableIndex; variableIndex++) { if (pushedValue.equals(variables.load(variableIndex))) { replaceVariablePushInstruction(clazz, offset, instruction, Instruction.OP_ILOAD, variableIndex); break; } } } } /** * Replaces the long pushing instruction at the given offset by a simpler * push instruction, if possible. */ private void replaceLongPushInstruction(Clazz clazz, int offset, Instruction instruction) { replaceLongPushInstruction(clazz, offset, instruction, partialEvaluator.getVariablesBefore(offset).size()); } /** * Replaces the long pushing instruction at the given offset by a simpler * push instruction, if possible. */ private void replaceLongPushInstruction(Clazz clazz, int offset, Instruction instruction, int maxVariableIndex) { Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0); if (pushedValue.isParticular()) { // Push a constant instead. long value = pushedValue.longValue().value(); if (value == 0L || value == 1L) { replaceConstantPushInstruction(clazz, offset, instruction, Instruction.OP_LCONST_0, (int)value); } else { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); Instruction replacementInstruction = new ConstantInstruction(Instruction.OP_LDC2_W, constantPoolEditor.addLongConstant(value)); replaceInstruction(clazz, offset, instruction, replacementInstruction); } } else if (pushedValue.isSpecific()) { // Load an equivalent lower-numbered variable instead, if any. TracedVariables variables = partialEvaluator.getVariablesBefore(offset); for (int variableIndex = 0; variableIndex < maxVariableIndex; variableIndex++) { // Note that we have to check the second part as well. if (pushedValue.equals(variables.load(variableIndex)) && variables.load(variableIndex + 1) != null && variables.load(variableIndex + 1).computationalType() == Value.TYPE_TOP) { replaceVariablePushInstruction(clazz, offset, instruction, Instruction.OP_LLOAD, variableIndex); } } } } /** * Replaces the float pushing instruction at the given offset by a simpler * push instruction, if possible. */ private void replaceFloatPushInstruction(Clazz clazz, int offset, Instruction instruction) { replaceFloatPushInstruction(clazz, offset, instruction, partialEvaluator.getVariablesBefore(offset).size()); } /** * Replaces the float pushing instruction at the given offset by a simpler * push instruction, if possible. */ private void replaceFloatPushInstruction(Clazz clazz, int offset, Instruction instruction, int maxVariableIndex) { Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0); if (pushedValue.isParticular()) { // Push a constant instead. // Make sure to distinguish between +0.0 and -0.0. float value = pushedValue.floatValue().value(); if (value == 0.0f && Float.floatToIntBits(value) == POS_ZERO_FLOAT_BITS || value == 1.0f || value == 2.0f) { replaceConstantPushInstruction(clazz, offset, instruction, Instruction.OP_FCONST_0, (int)value); } else { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); Instruction replacementInstruction = new ConstantInstruction(Instruction.OP_LDC, constantPoolEditor.addFloatConstant(value)); replaceInstruction(clazz, offset, instruction, replacementInstruction); } } else if (pushedValue.isSpecific()) { // Load an equivalent lower-numbered variable instead, if any. TracedVariables variables = partialEvaluator.getVariablesBefore(offset); for (int variableIndex = 0; variableIndex < maxVariableIndex; variableIndex++) { if (pushedValue.equals(variables.load(variableIndex))) { replaceVariablePushInstruction(clazz, offset, instruction, Instruction.OP_FLOAD, variableIndex); } } } } /** * Replaces the double pushing instruction at the given offset by a simpler * push instruction, if possible. */ private void replaceDoublePushInstruction(Clazz clazz, int offset, Instruction instruction) { replaceDoublePushInstruction(clazz, offset, instruction, partialEvaluator.getVariablesBefore(offset).size()); } /** * Replaces the double pushing instruction at the given offset by a simpler * push instruction, if possible. */ private void replaceDoublePushInstruction(Clazz clazz, int offset, Instruction instruction, int maxVariableIndex) { Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0); if (pushedValue.isParticular()) { // Push a constant instead. // Make sure to distinguish between +0.0 and -0.0. double value = pushedValue.doubleValue().value(); if (value == 0.0 && Double.doubleToLongBits(value) == POS_ZERO_DOUBLE_BITS || value == 1.0) { replaceConstantPushInstruction(clazz, offset, instruction, Instruction.OP_DCONST_0, (int)value); } else { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); Instruction replacementInstruction = new ConstantInstruction(Instruction.OP_LDC2_W, constantPoolEditor.addDoubleConstant(value)); replaceInstruction(clazz, offset, instruction, replacementInstruction); } } else if (pushedValue.isSpecific()) { // Load an equivalent lower-numbered variable instead, if any. TracedVariables variables = partialEvaluator.getVariablesBefore(offset); for (int variableIndex = 0; variableIndex < maxVariableIndex; variableIndex++) { // Note that we have to check the second part as well. if (pushedValue.equals(variables.load(variableIndex)) && variables.load(variableIndex + 1) != null && variables.load(variableIndex + 1).computationalType() == Value.TYPE_TOP) { replaceVariablePushInstruction(clazz, offset, instruction, Instruction.OP_DLOAD, variableIndex); } } } } /** * Replaces the reference pushing instruction at the given offset by a * simpler push instruction, if possible. */ private void replaceReferencePushInstruction(Clazz clazz, int offset, Instruction instruction) { ReferenceValue pushedValue = partialEvaluator.getStackAfter(offset).getTop(0).referenceValue(); if (pushedValue.isNull() == Value.ALWAYS) { // A reference value can only be specific if it is null. replaceConstantPushInstruction(clazz, offset, instruction, Instruction.OP_ACONST_NULL, 0); } } /** * Replaces the instruction at a given offset by a given push instruction * of a constant. */ private void replaceConstantPushInstruction(Clazz clazz, int offset, Instruction instruction, byte replacementOpcode, int value) { Instruction replacementInstruction = new SimpleInstruction(replacementOpcode, value); replaceInstruction(clazz, offset, instruction, replacementInstruction); } /** * Replaces the instruction at a given offset by a given push instruction * of a variable. */ private void replaceVariablePushInstruction(Clazz clazz, int offset, Instruction instruction, byte replacementOpcode, int variableIndex) { Instruction replacementInstruction = new VariableInstruction(replacementOpcode, variableIndex); replaceInstruction(clazz, offset, instruction, replacementInstruction); } /** * Replaces the given 'jsr' instruction by a simpler branch instruction, * if it jumps to a subroutine that doesn't return or a subroutine that * is only called from one place. */ private void replaceJsrInstruction(Clazz clazz, int offset, BranchInstruction branchInstruction) { // Is the subroutine ever returning? int subroutineStart = offset + branchInstruction.branchOffset; if (!partialEvaluator.isSubroutineReturning(subroutineStart) || partialEvaluator.branchOrigins(subroutineStart).instructionOffsetCount() == 1) { // All 'jsr' instructions to this subroutine can be replaced // by unconditional branch instructions. replaceBranchInstruction(clazz, offset, branchInstruction); } else if (!partialEvaluator.isTraced(offset + branchInstruction.length(offset))) { // We have to make sure the instruction after this 'jsr' // instruction is valid, even if it is never reached. replaceByInfiniteLoop(clazz, offset + branchInstruction.length(offset), branchInstruction); } } /** * Deletes the reference popping instruction at the given offset, if * it is at the start of a subroutine that doesn't return or a subroutine * that is only called from one place. */ private void deleteReferencePopInstruction(Clazz clazz, int offset, Instruction instruction) { if (partialEvaluator.isSubroutineStart(offset) && (!partialEvaluator.isSubroutineReturning(offset) || partialEvaluator.branchOrigins(offset).instructionOffsetCount() == 1)) { logger.debug(" Deleting store of subroutine return address {}", instruction.toString(offset)); // A reference value can only be specific if it is null. codeAttributeEditor.deleteInstruction(offset); } } /** * Deletes the given branch instruction, or replaces it by a simpler branch * instruction, if possible. */ private void replaceBranchInstruction(Clazz clazz, int offset, Instruction instruction) { InstructionOffsetValue branchTargets = partialEvaluator.branchTargets(offset); // Is there exactly one branch target (not from a goto or jsr)? if (branchTargets != null && branchTargets.instructionOffsetCount() == 1) { // Is it branching to the next instruction? int branchOffset = branchTargets.instructionOffset(0) - offset; if (branchOffset == instruction.length(offset)) { logger.debug(" Ignoring zero branch instruction at [{}]", offset); } else { // Replace the branch instruction by a simple branch instruction. Instruction replacementInstruction = new BranchInstruction(Instruction.OP_GOTO, branchOffset); replaceInstruction(clazz, offset, instruction, replacementInstruction); } } } /** * Replaces the given table switch instruction, if it is based on the value * of a fixed array. This is typical for switches on simple enums. */ private void replaceSimpleEnumSwitchInstruction(Clazz clazz, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction) { // Check if the switch instruction is consuming a single value loaded // from a fully specified array. InstructionOffsetValue producerOffsets = partialEvaluator.getStackBefore(offset).getTopProducerValue(0).instructionOffsetValue(); if (producerOffsets.instructionOffsetCount() == 1) { int producerOffset = producerOffsets.instructionOffset(0); if (codeAttribute.code[producerOffset] == Instruction.OP_IALOAD && !codeAttributeEditor.isModified(producerOffset)) { ReferenceValue referenceValue = partialEvaluator.getStackBefore(producerOffset).getTop(1).referenceValue(); if (referenceValue.isParticular()) { // Simplify the entire construct. replaceSimpleEnumSwitchInstruction(clazz, codeAttribute, producerOffset, offset, tableSwitchInstruction, referenceValue); } } } } /** * Replaces the given table switch instruction that is based on a value of * the given fixed array. */ private void replaceSimpleEnumSwitchInstruction(Clazz clazz, CodeAttribute codeAttribute, int loadOffset, int switchOffset, TableSwitchInstruction tableSwitchInstruction, ReferenceValue mappingValue) { ValueFactory valueFactory = new ParticularValueFactory(); // Transform the jump offsets. int[] jumpOffsets = tableSwitchInstruction.jumpOffsets; int[] newJumpOffsets = new int[mappingValue.arrayLength(valueFactory).value()]; for (int index = 0; index < newJumpOffsets.length; index++) { int switchCase = mappingValue.integerArrayLoad(valueFactory.createIntegerValue(index), valueFactory).value(); newJumpOffsets[index] = switchCase >= tableSwitchInstruction.lowCase && switchCase <= tableSwitchInstruction.highCase ? jumpOffsets[switchCase - tableSwitchInstruction.lowCase] : tableSwitchInstruction.defaultOffset; } // Update the instruction. tableSwitchInstruction.lowCase = 0; tableSwitchInstruction.highCase = newJumpOffsets.length - 1; tableSwitchInstruction.jumpOffsets = newJumpOffsets; // Replace the original one with the new version. replaceSimpleEnumSwitchInstruction(clazz, loadOffset, switchOffset, tableSwitchInstruction); cleanUpSwitchInstruction(clazz, switchOffset, tableSwitchInstruction); trimSwitchInstruction(clazz, switchOffset, tableSwitchInstruction); } /** * Replaces the given look up switch instruction, if it is based on the * value of a fixed array. This is typical for switches on simple enums. */ private void replaceSimpleEnumSwitchInstruction(Clazz clazz, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookupSwitchInstruction) { // Check if the switch instruction is consuming a single value loaded // from a fully specified array. InstructionOffsetValue producerOffsets = partialEvaluator.getStackBefore(offset).getTopProducerValue(0).instructionOffsetValue(); if (producerOffsets.instructionOffsetCount() == 1) { int producerOffset = producerOffsets.instructionOffset(0); if (codeAttribute.code[producerOffset] == Instruction.OP_IALOAD && !codeAttributeEditor.isModified(producerOffset)) { ReferenceValue referenceValue = partialEvaluator.getStackBefore(producerOffset).getTop(1).referenceValue(); if (referenceValue.isParticular()) { // Simplify the entire construct. replaceSimpleEnumSwitchInstruction(clazz, codeAttribute, producerOffset, offset, lookupSwitchInstruction, referenceValue); } } } } /** * Replaces the given look up switch instruction that is based on a value of * the given fixed array. This is typical for switches on simple enums. */ private void replaceSimpleEnumSwitchInstruction(Clazz clazz, CodeAttribute codeAttribute, int loadOffset, int switchOffset, LookUpSwitchInstruction lookupSwitchInstruction, ReferenceValue mappingValue) { ValueFactory valueFactory = new ParticularValueFactory(); // Transform the jump offsets. int[] cases = lookupSwitchInstruction.cases; int[] jumpOffsets = lookupSwitchInstruction.jumpOffsets; int[] newJumpOffsets = new int[mappingValue.arrayLength(valueFactory).value()]; for (int index = 0; index < newJumpOffsets.length; index++) { int switchCase = mappingValue.integerArrayLoad(valueFactory.createIntegerValue(index), valueFactory).value(); int caseIndex = Arrays.binarySearch(cases, switchCase); newJumpOffsets[index] = caseIndex >= 0 ? jumpOffsets[caseIndex] : lookupSwitchInstruction.defaultOffset; } // Replace the original lookup switch with a table switch. TableSwitchInstruction replacementSwitchInstruction = new TableSwitchInstruction(Instruction.OP_TABLESWITCH, lookupSwitchInstruction.defaultOffset, 0, newJumpOffsets.length - 1, newJumpOffsets); replaceSimpleEnumSwitchInstruction(clazz, loadOffset, switchOffset, replacementSwitchInstruction); cleanUpSwitchInstruction(clazz, switchOffset, replacementSwitchInstruction); trimSwitchInstruction(clazz, switchOffset, replacementSwitchInstruction); } /** * Makes sure all branch targets of the given switch instruction are valid. */ private void cleanUpSwitchInstruction(Clazz clazz, int offset, SwitchInstruction switchInstruction) { // Get the actual branch targets. InstructionOffsetValue branchTargets = partialEvaluator.branchTargets(offset); // Get an offset that can serve as a valid default offset. int defaultOffset = branchTargets.instructionOffset(branchTargets.instructionOffsetCount()-1) - offset; Instruction replacementInstruction = null; // Check the jump offsets. int[] jumpOffsets = switchInstruction.jumpOffsets; for (int index = 0; index < jumpOffsets.length; index++) { if (!branchTargets.contains(offset + jumpOffsets[index])) { // Replace the unused offset. jumpOffsets[index] = defaultOffset; // Remember to replace the instruction. replacementInstruction = switchInstruction; } } // Check the default offset. if (!branchTargets.contains(offset + switchInstruction.defaultOffset)) { // Replace the unused offset. switchInstruction.defaultOffset = defaultOffset; // Remember to replace the instruction. replacementInstruction = switchInstruction; } if (replacementInstruction != null) { replaceInstruction(clazz, offset, switchInstruction, replacementInstruction); } } /** * Trims redundant offsets from the given switch instruction. */ private void trimSwitchInstruction(Clazz clazz, int offset, TableSwitchInstruction tableSwitchInstruction) { // Get an offset that can serve as a valid default offset. int defaultOffset = tableSwitchInstruction.defaultOffset; int[] jumpOffsets = tableSwitchInstruction.jumpOffsets; int length = jumpOffsets.length; // Find the lowest index with a non-default jump offset. int lowIndex = 0; while (lowIndex < length && jumpOffsets[lowIndex] == defaultOffset) { lowIndex++; } // Find the highest index with a non-default jump offset. int highIndex = length - 1; while (highIndex >= 0 && jumpOffsets[highIndex] == defaultOffset) { highIndex--; } // Can we use a shorter array? int newLength = highIndex - lowIndex + 1; if (newLength < length) { if (newLength <= 0) { // Replace the switch instruction by a simple branch instruction. Instruction replacementInstruction = new BranchInstruction(Instruction.OP_GOTO, defaultOffset); replaceInstruction(clazz, offset, tableSwitchInstruction, replacementInstruction); } else { // Trim the array. int[] newJumpOffsets = new int[newLength]; System.arraycopy(jumpOffsets, lowIndex, newJumpOffsets, 0, newLength); tableSwitchInstruction.jumpOffsets = newJumpOffsets; tableSwitchInstruction.lowCase += lowIndex; tableSwitchInstruction.highCase -= length - newLength - lowIndex; replaceInstruction(clazz, offset, tableSwitchInstruction, tableSwitchInstruction); } } } /** * Trims redundant offsets from the given switch instruction. */ private void trimSwitchInstruction(Clazz clazz, int offset, LookUpSwitchInstruction lookUpSwitchInstruction) { // Get an offset that can serve as a valid default offset. int defaultOffset = lookUpSwitchInstruction.defaultOffset; int[] jumpOffsets = lookUpSwitchInstruction.jumpOffsets; int length = jumpOffsets.length; int newLength = length; // Count the default jump offsets. for (int index = 0; index < length; index++) { if (jumpOffsets[index] == defaultOffset) { newLength--; } } // Can we use shorter arrays? if (newLength < length) { if (newLength <= 0) { // Replace the switch instruction by a simple branch instruction. Instruction replacementInstruction = new BranchInstruction(Instruction.OP_GOTO, defaultOffset); replaceInstruction(clazz, offset, lookUpSwitchInstruction, replacementInstruction); } else { // Remove redundant entries from the arrays. int[] cases = lookUpSwitchInstruction.cases; int[] newJumpOffsets = new int[newLength]; int[] newCases = new int[newLength]; int newIndex = 0; for (int index = 0; index < length; index++) { if (jumpOffsets[index] != defaultOffset) { newJumpOffsets[newIndex] = jumpOffsets[index]; newCases[newIndex++] = cases[index]; } } lookUpSwitchInstruction.jumpOffsets = newJumpOffsets; lookUpSwitchInstruction.cases = newCases; replaceInstruction(clazz, offset, lookUpSwitchInstruction, lookUpSwitchInstruction); } } } /** * Checks whether if the current top value on the stack is a divisor * leading to a certain division by zero for the given computation type. */ private boolean isDivisionByZero(int offset, int computationType) { TracedStack tracedStack = partialEvaluator.getStackBefore(offset); Value divisor = tracedStack.getTop(0); switch (computationType) { case Value.TYPE_INTEGER: return divisor.computationalType() == Value.TYPE_INTEGER && divisor.isParticular() && divisor.integerValue().value() == 0; case Value.TYPE_LONG: return divisor.computationalType() == Value.TYPE_LONG && divisor.isParticular() && divisor.longValue().value() == 0L; case Value.TYPE_FLOAT: return divisor.computationalType() == Value.TYPE_FLOAT && divisor.isParticular() && divisor.floatValue().value() == 0f; case Value.TYPE_DOUBLE: return divisor.computationalType() == Value.TYPE_DOUBLE && divisor.isParticular() && divisor.doubleValue().value() == 0d; default: return false; } } /** * Checks whether the value at the given stack entry index is always a null reference. */ private boolean isNullReference(int offset, int popStackEntryIndex) { TracedStack tracedStack = partialEvaluator.getStackBefore(offset); Value objectRef = tracedStack.getTop(popStackEntryIndex); return objectRef.computationalType() == Value.TYPE_REFERENCE && objectRef.isParticular() && objectRef.referenceValue().isNull() == Value.ALWAYS; } /** * Replaces the given instruction by an explicit exception. */ private void replaceByException(Clazz clazz, int offset, Instruction instruction, String exceptionClass) { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); // Replace the instruction by an infinite loop. Instruction[] replacementInstructions = new Instruction[] { new ConstantInstruction(Instruction.OP_NEW, constantPoolEditor.addClassConstant(exceptionClass, null)), new SimpleInstruction(Instruction.OP_DUP), new ConstantInstruction(Instruction.OP_INVOKESPECIAL, constantPoolEditor.addMethodrefConstant(exceptionClass, "", "()V", null, null)), new SimpleInstruction(Instruction.OP_ATHROW) }; logger.debug(" Replacing instruction by explicit exception {}", exceptionClass); codeAttributeEditor.replaceInstruction(offset, replacementInstructions); // Visit the instruction, if required. if (extraInstructionVisitor != null) { // Note: we're not passing the right arguments for now, knowing that // they aren't used anyway. instruction.accept(clazz, null, null, offset, extraInstructionVisitor); } } /** * Replaces the given instruction by an infinite loop. */ private void replaceByInfiniteLoop(Clazz clazz, int offset, Instruction instruction) { // Replace the instruction by an infinite loop. Instruction replacementInstruction = new BranchInstruction(Instruction.OP_GOTO, 0); logger.debug(" Replacing unreachable instruction by infinite loop {}", replacementInstruction.toString(offset)); codeAttributeEditor.replaceInstruction(offset, replacementInstruction); // Visit the instruction, if required. if (extraInstructionVisitor != null) { // Note: we're not passing the right arguments for now, knowing that // they aren't used anyway. instruction.accept(clazz, null, null, offset, extraInstructionVisitor); } } /** * Replaces the instruction at a given offset by a given push instruction. */ private void replaceInstruction(Clazz clazz, int offset, Instruction instruction, Instruction replacementInstruction) { // Pop unneeded stack entries if necessary. int popCount = instruction.stackPopCount(clazz) - replacementInstruction.stackPopCount(clazz); insertPopInstructions(offset, popCount); logger.debug(" Replacing instruction {} -> {}{}", instruction.toString(offset), replacementInstruction.toString(), popCount == 0 ? "" : " ("+popCount+" pops)" ); codeAttributeEditor.replaceInstruction(offset, replacementInstruction); // Visit the instruction, if required. if (extraInstructionVisitor != null) { // Note: we're not passing the right arguments for now, knowing that // they aren't used anyway. instruction.accept(clazz, null, null, offset, extraInstructionVisitor); } } /** * Pops the given number of stack entries before the instruction at the * given offset. */ private void insertPopInstructions(int offset, int popCount) { switch (popCount) { case 0: { break; } case 1: { // Insert a single pop instruction. Instruction popInstruction = new SimpleInstruction(Instruction.OP_POP); codeAttributeEditor.insertBeforeInstruction(offset, popInstruction); break; } case 2: { // Insert a single pop2 instruction. Instruction popInstruction = new SimpleInstruction(Instruction.OP_POP2); codeAttributeEditor.insertBeforeInstruction(offset, popInstruction); break; } default: { // Insert the specified number of pop instructions. Instruction[] popInstructions = new Instruction[popCount / 2 + popCount % 2]; Instruction popInstruction = new SimpleInstruction(Instruction.OP_POP2); for (int index = 0; index < popCount / 2; index++) { popInstructions[index] = popInstruction; } if (popCount % 2 == 1) { popInstruction = new SimpleInstruction(Instruction.OP_POP); popInstructions[popCount / 2] = popInstruction; } codeAttributeEditor.insertBeforeInstruction(offset, popInstructions); break; } } } /** * Replaces the simple enum switch instructions at a given offsets by a * given replacement instruction. */ private void replaceSimpleEnumSwitchInstruction(Clazz clazz, int loadOffset, int switchOffset, SwitchInstruction replacementSwitchInstruction) { logger.debug(" Replacing switch instruction at [{}] -> [{}] swap + pop, {})", switchOffset, loadOffset, replacementSwitchInstruction.toString(switchOffset) ); // Remove the array load instruction. codeAttributeEditor.replaceInstruction(loadOffset, new Instruction[] { new SimpleInstruction(Instruction.OP_SWAP), new SimpleInstruction(Instruction.OP_POP), }); // Replace the switch instruction. codeAttributeEditor.replaceInstruction(switchOffset, replacementSwitchInstruction); // Visit the instruction, if required. if (extraInstructionVisitor != null) { // Note: we're not passing the right arguments for now, knowing that // they aren't used anyway. replacementSwitchInstruction.accept(clazz, null, null, switchOffset, extraInstructionVisitor); } } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/InstructionUsageMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.evaluation; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.ClassEstimates; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.evaluation.*; import proguard.evaluation.value.*; import proguard.optimize.info.*; import proguard.util.ArrayUtil; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; /** * This AttributeVisitor marks necessary instructions in the code attributes * that it visits, based on partial evaluation. * * @see NoSideEffectClassMarker * @see SideEffectClassMarker * @see ReadWriteFieldMarker * @see NoSideEffectMethodMarker * @see NoExternalSideEffectMethodMarker * @see SideEffectMethodMarker * @see ParameterEscapeMarker * * @author Eric Lafortune */ public class InstructionUsageMarker implements AttributeVisitor { private static final Logger logger = LogManager.getLogger(InstructionUsageMarker.class); private final PartialEvaluator partialEvaluator; private final boolean runPartialEvaluator; private final boolean ensureSafetyForVerifier; private final boolean markExternalSideEffects; private final PartialEvaluator simplePartialEvaluator; private final SideEffectInstructionChecker sideEffectInstructionChecker; private final MyParameterUsageMarker parameterUsageMarker = new MyParameterUsageMarker(); private final MyInitialUsageMarker initialUsageMarker = new MyInitialUsageMarker(); private final MyProducerMarker producerMarker = new MyProducerMarker(); private final MyVariableInitializationMarker variableInitializationMarker = new MyVariableInitializationMarker(); private final MyStackConsistencyMarker stackConsistencyMarker = new MyStackConsistencyMarker(); private final MyExtraPushPopInstructionMarker extraPushPopInstructionMarker = new MyExtraPushPopInstructionMarker(); private InstructionOffsetValue[] reverseDependencies = new InstructionOffsetValue[ClassEstimates.TYPICAL_CODE_LENGTH]; private boolean[][] stacksNecessaryAfter = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH][ClassEstimates.TYPICAL_STACK_SIZE]; private boolean[][] stacksUnwantedBefore = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH][ClassEstimates.TYPICAL_STACK_SIZE]; private boolean[] instructionsNecessary = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; private boolean[] extraPushPopInstructionsNecessary = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; private int maxMarkedOffset; /** * Creates a new InstructionUsageMarker. * * @param markExternalSideEffects specifies whether instructions with external side effects should be marked */ public InstructionUsageMarker(boolean markExternalSideEffects) { this(new PartialEvaluator(), true, true, markExternalSideEffects); } /** * Creates a new InstructionUsageMarker. * @param partialEvaluator the evaluator to be used for the analysis. * @param runPartialEvaluator specifies whether to run this evaluator on * every code attribute that is visited. * @param markExternalSideEffects specifies whether instructions with external side effects should be marked */ public InstructionUsageMarker(PartialEvaluator partialEvaluator, boolean runPartialEvaluator, boolean markExternalSideEffects) { this(partialEvaluator, runPartialEvaluator, true, markExternalSideEffects); } /** * Creates a new InstructionUsageMarker. * @param partialEvaluator the evaluator to be used for the analysis. * @param runPartialEvaluator specifies whether to run this evaluator on * every code attribute that is visited. * @param ensureSafetyForVerifier some instructions are not necessary for * code correctness but are necessary for the * java verifier. This flag determines whether * these instructions are visited (default: true). * @param markExternalSideEffects specifies whether instructions with external side effects should be marked */ public InstructionUsageMarker(PartialEvaluator partialEvaluator, boolean runPartialEvaluator, boolean ensureSafetyForVerifier, boolean markExternalSideEffects) { this.partialEvaluator = partialEvaluator; this.runPartialEvaluator = runPartialEvaluator; this.ensureSafetyForVerifier = ensureSafetyForVerifier; this.markExternalSideEffects = markExternalSideEffects; this.sideEffectInstructionChecker = new SideEffectInstructionChecker(true, true, markExternalSideEffects); if (ensureSafetyForVerifier) { this.simplePartialEvaluator = new PartialEvaluator(new TypedReferenceValueFactory()); } else { this.simplePartialEvaluator = partialEvaluator; } } /** * Returns whether the specified instruction was traced in the most * recently analyzed code attribute. */ public boolean isTraced(int instructionOffset) { return partialEvaluator.isTraced(instructionOffset); } /** * Returns a filtering version of the given instruction visitor that only * visits traced instructions. */ public InstructionVisitor tracedInstructionFilter(InstructionVisitor instructionVisitor) { return partialEvaluator.tracedInstructionFilter(instructionVisitor); } /** * Returns a filtering version of the given instruction visitor that only * visits traced or untraced instructions. */ public InstructionVisitor tracedInstructionFilter(boolean traced, InstructionVisitor instructionVisitor) { return partialEvaluator.tracedInstructionFilter(traced, instructionVisitor); } /** * Returns whether the specified instruction is necessary in the most * recently analyzed code attribute. */ public boolean isInstructionNecessary(int instructionOffset) { return instructionsNecessary[instructionOffset]; } /** * Returns whether an extra push/pop instruction is required at the given * offset in the most recently analyzed code attribute. */ public boolean isExtraPushPopInstructionNecessary(int instructionOffset) { return extraPushPopInstructionsNecessary[instructionOffset]; } /** * Returns a filtering version of the given instruction visitor that only * visits necessary instructions. */ public InstructionVisitor necessaryInstructionFilter(InstructionVisitor instructionVisitor) { return necessaryInstructionFilter(true, instructionVisitor); } /** * Returns a filtering version of the given instruction visitor that only * visits necessary or unnecessary instructions. */ public InstructionVisitor necessaryInstructionFilter(boolean necessary, InstructionVisitor instructionVisitor) { return new MyNecessaryInstructionFilter(necessary, instructionVisitor); } /** * Returns the stack before execution of the instruction at the given * offset. */ public TracedStack getStackBefore(int instructionOffset) { return partialEvaluator.getStackBefore(instructionOffset); } /** * Returns the stack after execution of the instruction at the given * offset. */ public TracedStack getStackAfter(int instructionOffset) { return partialEvaluator.getStackAfter(instructionOffset); } /** * Returns whether the specified stack entry before the given offset is * unwanted, e.g. because it was intended as a method parameter that has * been removed. */ public boolean isStackEntryUnwantedBefore(int instructionOffset, int stackIndex) { return stacksUnwantedBefore[instructionOffset][stackIndex]; } /** * Returns whether the stack specified entries before the given offset are * present. */ public boolean isStackEntriesPresentBefore(int instructionOffset, int stackIndex1, int stackIndex2) { boolean present1 = isStackEntryPresentBefore(instructionOffset, stackIndex1); boolean present2 = isStackEntryPresentBefore(instructionOffset, stackIndex2); //if (present1 ^ present2) //{ // throw new UnsupportedOperationException("Can't handle partial use of dup2 instructions"); //} return present1 || present2; } /** * Returns whether the specified stack entry before the given offset is * present. * @param instructionOffset the offset of the stack entry to be checked. * @param stackIndex the index of the stack entry to be checked * (counting from the bottom). */ public boolean isStackEntryPresentBefore(int instructionOffset, int stackIndex) { TracedStack tracedStack = partialEvaluator.getStackBefore(instructionOffset); InstructionOffsetValue producerOffsets = tracedStack.getBottomProducerValue(stackIndex).instructionOffsetValue(); return isAnyStackEntryNecessaryAfter(producerOffsets, stackIndex); } /** * Returns whether the stack specified entries after the given offset are * necessary. */ public boolean isStackEntriesNecessaryAfter(int instructionOffset, int stackIndex1, int stackIndex2) { boolean present1 = isStackEntryNecessaryAfter(instructionOffset, stackIndex1); boolean present2 = isStackEntryNecessaryAfter(instructionOffset, stackIndex2); //if (present1 ^ present2) //{ // throw new UnsupportedOperationException("Can't handle partial use of dup2 instructions"); //} return present1 || present2; } /** * Returns whether any of the stack entries after the given offsets are * necessary. * @param instructionOffsets the offsets of the stack entries to be checked. * @param stackIndex the index of the stack entries to be checked * (counting from the bottom). */ public boolean isAnyStackEntryNecessaryAfter(InstructionOffsetValue instructionOffsets, int stackIndex) { int offsetCount = instructionOffsets.instructionOffsetCount(); for (int offsetIndex = 0; offsetIndex < offsetCount; offsetIndex++) { if (instructionOffsets.isExceptionHandler(offsetIndex) || isStackEntryNecessaryAfter(instructionOffsets.instructionOffset(offsetIndex), stackIndex)) { return true; } } return false; } /** * Returns whether the specified stack entry after the given offset is * necessary. * @param instructionOffset the offset of the stack entry to be checked. * @param stackIndex the index of the stack entry to be checked * (counting from the bottom). */ public boolean isStackEntryNecessaryAfter(int instructionOffset, int stackIndex) { return (instructionOffset & InstructionOffsetValue.EXCEPTION_HANDLER) != 0 || stacksNecessaryAfter[instructionOffset][stackIndex]; } /** * Returns the instruction offsets to which the given instruction offset * branches in the most recently analyzed code attribute. */ public InstructionOffsetValue branchTargets(int instructionOffset) { return partialEvaluator.branchTargets(instructionOffset); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // DEBUG = DEBUG_RESULTS = // clazz.getName().equals("abc/Def") && // method.getName(clazz).equals("abc"); // TODO: Remove this when the instruction usage marker has stabilized. // Catch any unexpected exceptions from the actual visiting method. try { // Process the code. visitCodeAttribute0(clazz, method, codeAttribute); } catch (RuntimeException ex) { logger.error("Unexpected error while marking instruction usage after partial evaluation:"); logger.error(" Class = [{}]", clazz.getName()); logger.error(" Method = [{}{}]", method.getName(clazz), method.getDescriptor(clazz)); logger.error(" Exception = [{}] ({})", ex.getClass().getName(), ex.getMessage()); logger.debug("{}", () -> { StringWriter sw = new StringWriter(); method.accept(clazz, new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); throw ex; } } public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) { logger.debug("InstructionUsageMarker [{}.{}{}]", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz)); // Initialize the necessary arrays. initializeNecessary(codeAttribute); // Evaluate the method. if (runPartialEvaluator) { partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); } if (ensureSafetyForVerifier) { // Evaluate the method the way the JVM verifier would do it. simplePartialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); } int codeLength = codeAttribute.u4codeLength; maxMarkedOffset = -1; // Mark any unused method parameters on the stack. logger.debug("Invocation simplification:"); codeAttribute.instructionsAccept(clazz, method, partialEvaluator.tracedInstructionFilter(parameterUsageMarker)); // Mark all essential instructions that have been encountered as used. // Also mark infinite loops and instructions that can have side effects. logger.debug("Usage initialization: "); codeAttribute.instructionsAccept(clazz, method, partialEvaluator.tracedInstructionFilter(initialUsageMarker)); // Globally mark instructions and their produced variables and stack // entries on which necessary instructions depend. // Instead of doing this recursively, we loop across all instructions, // starting at the highest previously unmarked instruction that has // been been marked. logger.debug("Usage marking:"); while (maxMarkedOffset >= 0) { int offset = maxMarkedOffset; maxMarkedOffset = offset - 1; if (partialEvaluator.isTraced(offset)) { if (isInstructionNecessary(offset)) { // Mark the stack/variable producers of this instruction/ Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); instruction.accept(clazz, method, codeAttribute, offset, producerMarker); // Also mark any reverse dependencies. markReverseDependencies(offset); } // Check if this instruction is a branch origin from a branch // that straddles some marked code. markStraddlingBranches(offset, partialEvaluator.branchTargets(offset), true); // Check if this instruction is a branch target from a branch // that straddles some marked code. markStraddlingBranches(offset, partialEvaluator.branchOrigins(offset), false); } if (maxMarkedOffset > offset) { logger.debug(" -> {}", maxMarkedOffset); } } // Mark variable initializations, even if they aren't strictly necessary. // The virtual machine's verification step is not smart enough to see // this, and may complain otherwise. logger.debug("Initialization marking: "); codeAttribute.instructionsAccept(clazz, method, necessaryInstructionFilter( variableInitializationMarker)); // Mark produced stack entries, in order to keep the stack consistent. logger.debug("Stack consistency fixing:"); maxMarkedOffset = codeLength - 1; while (maxMarkedOffset >= 0) { int offset = maxMarkedOffset; maxMarkedOffset = offset - 1; if (partialEvaluator.isTraced(offset)) { Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); instruction.accept(clazz, method, codeAttribute, offset, stackConsistencyMarker); // Check if this instruction is a branch origin from a branch // that straddles some marked code. markStraddlingBranches(offset, partialEvaluator.branchTargets(offset), true); // Check if this instruction is a branch target from a branch // that straddles some marked code. markStraddlingBranches(offset, partialEvaluator.branchOrigins(offset), false); } } // Mark unnecessary popping instructions, in order to keep the stack // consistent. logger.debug("Extra pop marking:"); maxMarkedOffset = codeLength - 1; while (maxMarkedOffset >= 0) { int offset = maxMarkedOffset; maxMarkedOffset = offset - 1; if (partialEvaluator.isTraced(offset) && !isInstructionNecessary(offset)) { Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); instruction.accept(clazz, method, codeAttribute, offset, extraPushPopInstructionMarker); // Check if this instruction is a branch origin from a branch // that straddles some marked code. markStraddlingBranches(offset, partialEvaluator.branchTargets(offset), true); // Check if this instruction is a branch target from a branch // that straddles some marked code. markStraddlingBranches(offset, partialEvaluator.branchOrigins(offset), false); } } logger.debug("Instruction usage results:"); if (logger.getLevel().isLessSpecificThan(Level.DEBUG)) { int offset = 0; do { Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); logger.debug("{}{}", (isInstructionNecessary(offset) ? " + " : isExtraPushPopInstructionNecessary(offset) ? " ~ " : " - "), instruction.toString(offset)); offset += instruction.length(offset); } while (offset < codeLength); } } /** * This MemberVisitor marks stack entries that aren't necessary because * parameters aren't used in the methods that are visited. */ private class MyParameterUsageMarker implements InstructionVisitor, ConstantVisitor, MemberVisitor { private int parameterSize; private long usedParameters; // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: case Instruction.OP_INVOKEINTERFACE: { parameterSize = 0; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); // Mark unused parameters. for (int index = 0; index < parameterSize; index++) { if (index < 64 && (usedParameters & (1L << index)) == 0L) { TracedStack stack = partialEvaluator.getStackBefore(offset); int stackIndex = stack.size() - parameterSize + index; logger.debug(" [{}] Ignoring parameter #{} (stack entry #{} [{}])", offset, index, stackIndex, stack.getBottom(stackIndex)); logger.debug(" Full stack: {}", stack); markStackEntryUnwantedBefore(offset, stackIndex); } } break; } } } // Implementations for ConstantVisitor. public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { refConstant.referencedMemberAccept(this); } // Implementations for MemberVisitor. public void visitAnyMember(Clazz clazz, Member member) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Get the total size of the parameters and the mask of the used // parameters. parameterSize = ParameterUsageMarker.getParameterSize(programMethod); usedParameters = ParameterUsageMarker.getUsedParameters(programMethod); } } /** * This InstructionVisitor marks the instructions that are intrinsically * necessary, because they have side effects. */ private class MyInitialUsageMarker implements InstructionVisitor, ConstantVisitor, ParameterVisitor { private final MemberVisitor reverseDependencyCreator = new AllParameterVisitor(true, this); // Parameters and values for visitor methods. private int referencingOffset; private int referencingPopCount; // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { if (sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, instruction)) { markInstruction(offset); } } public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { switch (simpleInstruction.opcode) { case Instruction.OP_IASTORE: case Instruction.OP_LASTORE: case Instruction.OP_FASTORE: case Instruction.OP_DASTORE: case Instruction.OP_AASTORE: case Instruction.OP_BASTORE: case Instruction.OP_CASTORE: case Instruction.OP_SASTORE: createReverseDependencies(clazz, offset, simpleInstruction); // Also check for side-effects of the instruction itself. visitAnyInstruction(clazz, method, codeAttribute, offset, simpleInstruction); break; default: visitAnyInstruction(clazz, method, codeAttribute, offset, simpleInstruction); break; } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_ANEWARRAY: case Instruction.OP_MULTIANEWARRAY: // We may have to mark the instruction due to initializers. referencingOffset = offset; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); // Also check for side-effects of the instruction itself. visitAnyInstruction(clazz, method, codeAttribute, offset, constantInstruction); break; case Instruction.OP_LDC: case Instruction.OP_LDC_W: case Instruction.OP_NEW: case Instruction.OP_GETSTATIC: // We may have to mark the instruction due to initializers. referencingOffset = offset; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; case Instruction.OP_PUTFIELD: // We generally have to mark the putfield instruction, // unless it's never read. We can reverse the dependencies // if it's a field of a recently created instance. if (sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, constantInstruction)) { createReverseDependencies(clazz, offset, constantInstruction); } break; case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: case Instruction.OP_INVOKEINTERFACE: referencingOffset = offset; referencingPopCount = constantInstruction.stackPopCount(clazz); clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; default: visitAnyInstruction(clazz, method, codeAttribute, offset, constantInstruction); break; } } public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { if (branchInstruction.opcode == Instruction.OP_GOTO && branchInstruction.branchOffset == 0) { logger.debug("(infinite loop)"); markInstruction(offset); } else { visitAnyInstruction(clazz, method, codeAttribute, offset, branchInstruction); } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { Clazz referencedClass = stringConstant.referencedClass; // If a static initializer may have side effects, the instruction // has to be marked. if (referencedClass != null && SideEffectClassChecker.mayHaveSideEffects(clazz, referencedClass)) { // Mark the invocation. markInstruction(referencingOffset); } } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { Clazz referencedClass = classConstant.referencedClass; // If a static initializer may have side effects, the instruction // has to be marked. if (referencedClass == null || SideEffectClassChecker.mayHaveSideEffects(clazz, referencedClass)) { // Mark the invocation. markInstruction(referencingOffset); } } public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, this); } public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { Method referencedMethod = (Method)anyMethodrefConstant.referencedMethod; // if (referencedMethod != null) // { // System.out.println("InstructionUsageMarker$MyInitialUsageMarker.visitAnyMethodrefConstant [" + anyMethodrefConstant.getClassName(clazz) + "." + anyMethodrefConstant.getName(clazz) + // "]: mark! esc = " + ParameterEscapeMarker.getEscapingParameters(referencedMethod) + // ", mod = " + ParameterEscapeMarker.modifiesAnything(referencedMethod) + // ", side = " + SideEffectClassChecker.mayHaveSideEffects(clazz, // anyMethodrefConstant.referencedClass, // referencedMethod)); // } // Is the method invocation really necessary? if (markExternalSideEffects && referencedMethod != null && SideEffectMethodMarker.hasSideEffects(referencedMethod) && // Skip if the method was explicitly marked as having no external side-effects. !NoExternalSideEffectMethodMarker.hasNoExternalSideEffects(referencedMethod)) { // In case we shall optimize conservatively, always mark the method // call if the referenced method has side effects. markInstruction(referencingOffset); } else if (referencedMethod == null || ParameterEscapeMarker.getEscapingParameters(referencedMethod) != 0L || ParameterEscapeMarker.modifiesAnything(referencedMethod) || SideEffectClassChecker.mayHaveSideEffects(clazz, anyMethodrefConstant.referencedClass, referencedMethod)) { // System.out.println(" -> mark ["+referencingOffset+"]"); // Mark the invocation. markInstruction(referencingOffset); } else { logger.debug(" [{}] Checking parameters of [{}.{}{}] (pop count = {})", referencingOffset, anyMethodrefConstant.getClassName(clazz), anyMethodrefConstant.getName(clazz), anyMethodrefConstant.getType(clazz), referencingPopCount ); // Create reverse dependencies for reference parameters that // are modified. anyMethodrefConstant.referencedMethodAccept(reverseDependencyCreator); } } // Implementations for ParameterVisitor. public void visitParameter(Clazz clazz, Member member, int parameterIndex, int parameterCount, int parameterOffset, int parameterSize, String parameterType, Clazz referencedClass) { Method method = (Method)member; logger.debug(" P{}: escaping = {}, modified = {}, returned = {}", parameterIndex, ParameterEscapeMarker.isParameterEscaping(method, parameterIndex), ParameterEscapeMarker.isParameterModified(method, parameterIndex), ParameterEscapeMarker.isParameterReturned(method, parameterIndex) ); // Create a reverse dependency if the reference parameter is // modified. if (ParameterEscapeMarker.isParameterModified(method, parameterIndex)) { createReverseDependencies(referencingOffset, parameterSize - parameterOffset - 1); } } /** * Marks the specified instruction offset or creates reverse * dependencies to the producers of its bottom popped stack entry. */ private void createReverseDependencies(Clazz clazz, int offset, Instruction instruction) { createReverseDependencies(offset, instruction.stackPopCount(clazz) - 1); } /** * Marks the specified instruction offset or creates reverse * dependencies to the producers of the specified stack entry, if it * is a reference value. */ private void createReverseDependencies(int offset, int stackEntryIndex) { TracedStack stackBefore = partialEvaluator.getStackBefore(offset); Value stackEntry = stackBefore.getTop(stackEntryIndex); // System.out.println(" ["+offset+"] s"+stackEntryIndex+": ["+stackEntry+"]"); if (stackEntry.computationalType() == Value.TYPE_REFERENCE) { ReferenceValue referenceValue = stackEntry.referenceValue(); // System.out.println("EvaluationShrinker$MyInitialUsageMarker.createReverseDependencies: ["+offset+"] ["+referenceValue+"]?"); // The null reference value may not have a trace value. if (referenceValue.isNull() != Value.ALWAYS) { if (referenceValue instanceof TracedReferenceValue) { TracedReferenceValue tracedReferenceValue = (TracedReferenceValue)referenceValue; createReverseDependencies(offset, tracedReferenceValue.getTraceValue().instructionOffsetValue()); } else { // System.out.println("InstructionUsageMarker$MyInitialUsageMarker.createReverseDependencies: not a TracedReferenceValue"); markInstruction(offset); } } } } /** * Marks the specified instruction offset or creates reverse * dependencies to the producers of the given reference value. */ private void createReverseDependencies(int offset, InstructionOffsetValue producerOffsets) { InstructionOffsetValue consumerOffset = new InstructionOffsetValue(offset); int offsetCount = producerOffsets.instructionOffsetCount(); for (int offsetIndex = 0; offsetIndex < offsetCount; offsetIndex++) { if (producerOffsets.isNewinstance(offsetIndex)) { // Create a reverse dependency. If the creating instruction // is necessary, then so is this one. int producerOffset = producerOffsets.instructionOffset(offsetIndex); // Avoid circular dependencies in code that loops with // instances on the stack (like the string encryption code). if (producerOffset != offset) { logger.debug(" Inserting reverse dependency from instance producers [{}] to [{}]", producerOffset, offset); InstructionOffsetValue reverseDependency = reverseDependencies[producerOffset]; reverseDependencies[producerOffset] = reverseDependency == null ? consumerOffset : reverseDependency.generalize(consumerOffset); } } else { // Just mark the instruction. markInstruction(offset); } } } } /** * This InstructionVisitor marks the producing instructions and produced * variables and stack entries of the instructions that it visits. * Simplified method arguments are ignored. */ private class MyProducerMarker implements InstructionVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { markStackProducers(clazz, offset, instruction); } public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { switch (simpleInstruction.opcode) { case Instruction.OP_DUP: conditionallyMarkStackEntryProducers(offset, 0, 0); conditionallyMarkStackEntryProducers(offset, 1, 0); break; case Instruction.OP_DUP_X1: conditionallyMarkStackEntryProducers(offset, 0, 0); conditionallyMarkStackEntryProducers(offset, 1, 1); conditionallyMarkStackEntryProducers(offset, 2, 0); break; case Instruction.OP_DUP_X2: conditionallyMarkStackEntryProducers(offset, 0, 0); conditionallyMarkStackEntryProducers(offset, 1, 1); conditionallyMarkStackEntryProducers(offset, 2, 2); conditionallyMarkStackEntryProducers(offset, 3, 0); break; case Instruction.OP_DUP2: conditionallyMarkStackEntryProducers(offset, 0, 0); conditionallyMarkStackEntryProducers(offset, 1, 1); conditionallyMarkStackEntryProducers(offset, 2, 0); conditionallyMarkStackEntryProducers(offset, 3, 1); break; case Instruction.OP_DUP2_X1: conditionallyMarkStackEntryProducers(offset, 0, 0); conditionallyMarkStackEntryProducers(offset, 1, 1); conditionallyMarkStackEntryProducers(offset, 2, 2); conditionallyMarkStackEntryProducers(offset, 3, 0); conditionallyMarkStackEntryProducers(offset, 4, 1); break; case Instruction.OP_DUP2_X2: conditionallyMarkStackEntryProducers(offset, 0, 0); conditionallyMarkStackEntryProducers(offset, 1, 1); conditionallyMarkStackEntryProducers(offset, 2, 2); conditionallyMarkStackEntryProducers(offset, 3, 3); conditionallyMarkStackEntryProducers(offset, 4, 0); conditionallyMarkStackEntryProducers(offset, 5, 1); break; case Instruction.OP_SWAP: conditionallyMarkStackEntryProducers(offset, 0, 1); conditionallyMarkStackEntryProducers(offset, 1, 0); break; default: markStackProducers(clazz, offset, simpleInstruction); break; } } public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { // Is the variable being loaded or incremented? if (variableInstruction.isLoad()) { markVariableProducers(offset, variableInstruction.variableIndex); } else { markStackProducers(clazz, offset, variableInstruction); } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { markStackProducers(clazz, offset, constantInstruction); } public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { // Explicitly mark the produced stack entry of a 'jsr' instruction, // because the consuming 'astore' instruction of the subroutine is // cleared every time it is traced. if (branchInstruction.opcode == Instruction.OP_JSR || branchInstruction.opcode == Instruction.OP_JSR_W) { markStackEntryAfter(offset, 0); } else { markStackProducers(clazz, offset, branchInstruction); } } } /** * This InstructionVisitor marks variable initializations that are * necessary to appease the JVM. */ private class MyVariableInitializationMarker implements InstructionVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { // Is the variable being loaded or incremented? if (variableInstruction.isLoad()) { // Mark any variable initializations for this variable load that // are required according to the JVM. markVariableInitializersBefore(offset, variableInstruction.variableIndex, null); } } } /** * This InstructionVisitor marks stack entries that should be pushed * (and previously unnecessary pushing instructions) to keep the stack * consistent at later points in the execution. */ private class MyStackConsistencyMarker implements InstructionVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // We check all entries to make sure the stack is also consistent // at method exit points, where some stack entries might be // discarded. int stackSize = partialEvaluator.getStackBefore(offset).size(); for (int stackIndex = 0; stackIndex < stackSize; stackIndex++) { // Is this stack entry pushed by any producer // (because it is required by other consumers)? if (!isStackEntryUnwantedBefore(offset, stackIndex) && isStackEntryPresentBefore(offset, stackIndex)) { // Mark all produced stack entries. markStackEntryProducers(offset, stackIndex, false); } } } } /** * This InstructionVisitor marks instructions that should still push or * pop some values to keep the stack consistent. */ private class MyExtraPushPopInstructionMarker implements InstructionVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // Check all stack entries that are popped. // // Typical case: a stack value that is required elsewhere or a // pushed exception type that still has to be popped. int stackSize = partialEvaluator.getStackBefore(offset).size(); int firstStackIndex = stackSize - instruction.stackPopCount(clazz); for (int stackIndex = firstStackIndex; stackIndex < stackSize; stackIndex++) { // Is this stack entry pushed by any producer // (because it is required by other consumers)? if (!isStackEntryUnwantedBefore(offset, stackIndex) && isStackEntryPresentBefore(offset, stackIndex)) { // Mark that we'll need an extra pop instruction. markExtraPushPopInstruction(offset); // [DGD-481][DGD-504] Mark the stack entries and // their producers again for a push/pop. In Kotlin // code, it can happen that we have missed a producer // during stack consistency marking. markStackEntryProducers(offset, stackIndex, false); } } } } // Small utility methods. /** * Marks the producing instructions of the variable consumer at the given * offset. * @param consumerOffset the offset of the variable consumer. * @param variableIndex the index of the variable that is loaded. */ private void markVariableProducers(int consumerOffset, int variableIndex) { InstructionOffsetValue producerOffsets = partialEvaluator.getVariablesBefore(consumerOffset).getProducerValue(variableIndex).instructionOffsetValue(); if (producerOffsets != null) { int offsetCount = producerOffsets.instructionOffsetCount(); for (int offsetIndex = 0; offsetIndex < offsetCount; offsetIndex++) { if (!producerOffsets.isMethodParameter(offsetIndex) && !producerOffsets.isExceptionHandler(offsetIndex)) { // Make sure the variable and the instruction are marked // at the producing offset. int offset = producerOffsets.instructionOffset(offsetIndex); markInstruction(offset); } } } } /** * Ensures that the given variable is initialized before the specified * consumer of that variable, in the JVM's view. * @param consumerOffset the instruction offset before which the variable * needs to be initialized. * @param variableIndex the index of the variable. * @param visitedOffsets the already visited consumer offsets, needed to * prevent infinite loops. * @return the updated visited consumer offsets. */ private InstructionOffsetValue markVariableInitializersBefore(int consumerOffset, int variableIndex, InstructionOffsetValue visitedOffsets) { // Avoid infinite loops by stopping recursion if we encounter // an already visited offset. if (visitedOffsets == null || !visitedOffsets.contains(consumerOffset)) { visitedOffsets = visitedOffsets == null ? new InstructionOffsetValue(consumerOffset) : visitedOffsets.add(consumerOffset); // Make sure the variable is initialized after all producers. // Use the simple evaluator, to get the JVM's view of what is // initialized. InstructionOffsetValue producerOffsets = simplePartialEvaluator.getVariablesBefore(consumerOffset).getProducerValue(variableIndex).instructionOffsetValue(); int offsetCount = producerOffsets.instructionOffsetCount(); for (int offsetIndex = 0; offsetIndex < offsetCount; offsetIndex++) { if (!producerOffsets.isMethodParameter(offsetIndex) && !producerOffsets.isExceptionHandler(offsetIndex)) { int producerOffset = producerOffsets.instructionOffset(offsetIndex); visitedOffsets = markVariableInitializersAfter(producerOffset, variableIndex, visitedOffsets); } } } return visitedOffsets; } /** * Ensures that the given variable is initialized after the specified * producer of that variable, in the JVM's view. * @param producerOffset the instruction offset after which the variable * needs to be initialized. * @param variableIndex the index of the variable. * @param visitedOffsets the already visited consumer offsets, needed to * prevent infinite loops. * @return the updated visited consumer offsets. */ private InstructionOffsetValue markVariableInitializersAfter(int producerOffset, int variableIndex, InstructionOffsetValue visitedOffsets) { // No problem if the producer has already been marked. if (!isInstructionNecessary(producerOffset)) { // Is the unmarked producer a variable initialization? if (isVariableInitialization(producerOffset, variableIndex)) { // Mark the producer. logger.debug(" Marking initialization of v{} at ", variableIndex); markInstruction(producerOffset); } else { // Don't mark the producer, but recursively look at the // preceding producers of the same variable. Their values // will fall through, replacing this producer. visitedOffsets = markVariableInitializersBefore(producerOffset, variableIndex, visitedOffsets); } } return visitedOffsets; } /** * Marks the stack entries and their producing instructions of the * consumer at the given offset. * @param clazz the containing class. * @param consumerOffset the offset of the consumer. * @param consumer the consumer of the stack entries. */ private void markStackProducers(Clazz clazz, int consumerOffset, Instruction consumer) { TracedStack tracedStack = partialEvaluator.getStackBefore(consumerOffset); int stackSize = tracedStack.size(); // Mark the producers of the popped values. int popCount = consumer.stackPopCount(clazz); for (int stackIndex = stackSize - popCount; stackIndex < stackSize; stackIndex++) { markStackEntryProducers(consumerOffset, stackIndex, true); } } /** * Marks the stack entry and the corresponding producing instructions * of the consumer at the given offset, if the stack entry of the * consumer is marked. * @param consumerOffset the offset of the consumer. * @param consumerTopStackIndex the index of the stack entry to be checked * (counting from the top). * @param producerTopStackIndex the index of the stack entry to be marked * (counting from the top). */ private void conditionallyMarkStackEntryProducers(int consumerOffset, int consumerTopStackIndex, int producerTopStackIndex) { int consumerBottomStackIndex = partialEvaluator.getStackAfter(consumerOffset).size() - consumerTopStackIndex - 1; if (isStackEntryNecessaryAfter(consumerOffset, consumerBottomStackIndex)) { int producerBottomStackIndex = partialEvaluator.getStackBefore(consumerOffset).size() - producerTopStackIndex - 1; markStackEntryProducers(consumerOffset, producerBottomStackIndex, true); } } /** * Marks the stack entry and optionally the corresponding producing * instructions of the consumer at the given offset. * @param consumerOffset the offset of the consumer. * @param stackIndex the index of the stack entry to be marked * (counting from the bottom). * @param markInstructions specifies whether the producing instructions * should be marked. */ private void markStackEntryProducers(int consumerOffset, int stackIndex, boolean markInstructions) { if (!isStackEntryUnwantedBefore(consumerOffset, stackIndex)) { markStackEntryProducers(partialEvaluator.getStackBefore(consumerOffset).getBottomProducerValue(stackIndex).instructionOffsetValue(), stackIndex, markInstructions); } } /** * Marks the stack entry and optionally its producing instructions at the * given offsets. * @param producerOffsets the offsets of the producers to be marked. * @param stackIndex the index of the stack entry to be marked * (counting from the bottom). * @param markInstructions specifies whether the producing instructions * should be marked. */ private void markStackEntryProducers(InstructionOffsetValue producerOffsets, int stackIndex, boolean markInstructions) { if (producerOffsets != null) { int offsetCount = producerOffsets.instructionOffsetCount(); for (int offsetIndex = 0; offsetIndex < offsetCount; offsetIndex++) { if (!producerOffsets.isExceptionHandler(offsetIndex)) { // Make sure the stack entry and the instruction are marked // at the producing offset. int offset = producerOffsets.instructionOffset(offsetIndex); markStackEntryAfter(offset, stackIndex); if (markInstructions) { // We can mark the producer. markInstruction(offset); } else { // We'll need to push a stack entry at that point // instead. markExtraPushPopInstruction(offset); } } } } } /** * Marks any modification instructions that are required by the specified * creation instruction (new, newarray, method returning new * instance,...), so this new instance is properly initialized. * @param instructionOffset the offset of the creation instruction. */ private void markReverseDependencies(int instructionOffset) { InstructionOffsetValue reverseDependency = reverseDependencies[instructionOffset]; if (reverseDependency != null) { markInstructions(reverseDependency); } } /** * Marks the branch instructions of straddling branches, if they straddle * some code that has been marked. * @param instructionOffset the offset of the branch origin or branch target. * @param branchOffsets the offsets of the straddling branch targets * or branch origins. * @param isPointingToTargets true if the above offsets are * branch targets, false if they * are branch origins. */ private void markStraddlingBranches(int instructionOffset, InstructionOffsetValue branchOffsets, boolean isPointingToTargets) { if (branchOffsets != null) { // Loop over all branch offsets. int branchCount = branchOffsets.instructionOffsetCount(); for (int branchIndex = 0; branchIndex < branchCount; branchIndex++) { // Is the branch straddling forward any necessary instructions? int branchOffset = branchOffsets.instructionOffset(branchIndex); // Is the offset pointing to a branch origin or to a branch target? if (isPointingToTargets) { markStraddlingBranch(instructionOffset, branchOffset, instructionOffset, branchOffset); } else { markStraddlingBranch(instructionOffset, branchOffset, branchOffset, instructionOffset); } } } } private void markStraddlingBranch(int instructionOffsetStart, int instructionOffsetEnd, int branchOrigin, int branchTarget) { if (!isInstructionNecessary(branchOrigin) && isAnyInstructionNecessary(instructionOffsetStart, instructionOffsetEnd)) { logger.debug("[{}->{}]", branchOrigin, branchTarget); // Mark the branch instruction. markInstruction(branchOrigin); } } /** * Initializes the necessary data structure. */ private void initializeNecessary(CodeAttribute codeAttribute) { int codeLength = codeAttribute.u4codeLength; int maxLocals = codeAttribute.u2maxLocals; int maxStack = codeAttribute.u2maxStack; // Create new arrays for storing information at each instruction offset. reverseDependencies = ArrayUtil.ensureArraySize(reverseDependencies, codeLength, null); if (stacksNecessaryAfter.length < codeLength || stacksNecessaryAfter[0].length < maxStack) { stacksNecessaryAfter = new boolean[codeLength][maxStack]; } else { for (int offset = 0; offset < codeLength; offset++) { Arrays.fill(stacksNecessaryAfter[offset], 0, maxStack, false); } } if (stacksUnwantedBefore.length < codeLength || stacksUnwantedBefore[0].length < maxStack) { stacksUnwantedBefore = new boolean[codeLength][maxStack]; } else { for (int offset = 0; offset < codeLength; offset++) { Arrays.fill(stacksUnwantedBefore[offset], 0, maxStack, false); } } instructionsNecessary = ArrayUtil.ensureArraySize(instructionsNecessary, codeLength, false); extraPushPopInstructionsNecessary = ArrayUtil.ensureArraySize(extraPushPopInstructionsNecessary, codeLength, false); } /** * Returns whether the specified variable is initialized at the specified * offset. */ private boolean isVariableInitialization(int instructionOffset, int variableIndex) { // Wasn't the variable set yet? Value valueBefore = simplePartialEvaluator.getVariablesBefore(instructionOffset).getValue(variableIndex); if (valueBefore == null) { return true; } // Is the computational type different now? Value valueAfter = simplePartialEvaluator.getVariablesAfter(instructionOffset).getValue(variableIndex); if (valueAfter.computationalType() != valueBefore.computationalType()) { return true; } // Is the reference type different now? if (valueAfter.computationalType() == Value.TYPE_REFERENCE && (valueAfter.referenceValue().isNull() == Value.ALWAYS || !valueAfter.referenceValue().getType().equals(valueBefore.referenceValue().getType()))) { return true; } // Was the producer an argument (which may be removed)? InstructionOffsetValue producersBefore = simplePartialEvaluator.getVariablesBefore(instructionOffset).getProducerValue(variableIndex).instructionOffsetValue(); return producersBefore.instructionOffsetCount() == 1 && producersBefore.isMethodParameter(0); } /** * Marks the stack entry after the given offset. * @param instructionOffset the offset of the stack entry to be marked. * @param stackIndex the index of the stack entry to be marked * (counting from the bottom). */ private void markStackEntryAfter(int instructionOffset, int stackIndex) { if (!isStackEntryNecessaryAfter(instructionOffset, stackIndex)) { logger.debug("[{}.s{}],", instructionOffset, stackIndex); stacksNecessaryAfter[instructionOffset][stackIndex] = true; if (maxMarkedOffset < instructionOffset) { maxMarkedOffset = instructionOffset; } } } /** * Marks the specified stack entry as unwanted, typically because it is * an unused parameter of a method invocation. * @param instructionOffset the offset of the consumer. * @param stackIndex the index of the stack entry to be marked * (counting from the bottom). */ private void markStackEntryUnwantedBefore(int instructionOffset, int stackIndex) { stacksUnwantedBefore[instructionOffset][stackIndex] = true; } /** * Marks the specified instructions as used. * @param instructionOffsets the offsets of the instructions. */ private void markInstructions(InstructionOffsetValue instructionOffsets) { int count = instructionOffsets.instructionOffsetCount(); for (int index = 0; index < count; index++) { markInstruction(instructionOffsets.instructionOffset(index)); } } /** * Marks the specified instruction as used. * @param instructionOffset the offset of the instruction. */ private void markInstruction(int instructionOffset) { if (!isInstructionNecessary(instructionOffset)) { logger.debug("{},", instructionOffset); instructionsNecessary[instructionOffset] = true; if (maxMarkedOffset < instructionOffset) { maxMarkedOffset = instructionOffset; } } } /** * Marks that an extra push/pop instruction is required at the given * offset, if the current instruction at that offset is unused. * @param instructionOffset the offset of the instruction. */ private void markExtraPushPopInstruction(int instructionOffset) { if (!isInstructionNecessary(instructionOffset) && !isExtraPushPopInstructionNecessary(instructionOffset)) { logger.debug("{},", instructionOffset); extraPushPopInstructionsNecessary[instructionOffset] = true; if (maxMarkedOffset < instructionOffset) { maxMarkedOffset = instructionOffset; } } } /** * Returns whether any instruction in the specified sequence of * instructions is necessary. * @param startInstructionOffset the start offset of the instruction * sequence (inclusive). * @param endInstructionOffset the end offset of the instruction * sequence (exclusive). * @return whether any instruction is necessary. */ private boolean isAnyInstructionNecessary(int startInstructionOffset, int endInstructionOffset) { for (int instructionOffset = startInstructionOffset; instructionOffset < endInstructionOffset; instructionOffset++) { if (isInstructionNecessary(instructionOffset) || isExtraPushPopInstructionNecessary(instructionOffset)) { return true; } } return false; } /** * This InstructionVisitor delegates its visits to a given * InstructionVisitor, but only if the instruction has been marked as * necessary (or not). */ private class MyNecessaryInstructionFilter implements InstructionVisitor { private final boolean necessary; private final InstructionVisitor instructionVisitor; public MyNecessaryInstructionFilter(boolean necessary, InstructionVisitor instructionVisitor) { this.necessary = necessary; this.instructionVisitor = instructionVisitor; } // Implementations for InstructionVisitor. public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { if (shouldVisit(offset)) { instructionVisitor.visitSimpleInstruction(clazz, method, codeAttribute, offset, simpleInstruction); } } public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { if (shouldVisit(offset)) { instructionVisitor.visitVariableInstruction(clazz, method, codeAttribute, offset, variableInstruction); } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (shouldVisit(offset)) { instructionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); } } public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { if (shouldVisit(offset)) { instructionVisitor.visitBranchInstruction(clazz, method, codeAttribute, offset, branchInstruction); } } public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction) { if (shouldVisit(offset)) { instructionVisitor.visitTableSwitchInstruction(clazz, method, codeAttribute, offset, tableSwitchInstruction); } } public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction) { if (shouldVisit(offset)) { instructionVisitor.visitLookUpSwitchInstruction(clazz, method, codeAttribute, offset, lookUpSwitchInstruction); } } // Small utility methods. /** * Returns whether the instruction at the given offset should be * visited, depending on whether it is necessary or not. */ private boolean shouldVisit(int offset) { return isInstructionNecessary(offset) == necessary; } } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/LoadingInvocationUnit.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.evaluation; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.evaluation.BasicInvocationUnit; import proguard.evaluation.value.*; /** * This InvocationUnit loads parameter values and return values that were * previously stored with the methods that are invoked. * * @see StoringInvocationUnit * @author Eric Lafortune */ public class LoadingInvocationUnit extends BasicInvocationUnit { private final boolean loadFieldValues; private final boolean loadMethodParameterValues; private final boolean loadMethodReturnValues; /** * Creates a new LoadingInvocationUnit with the given value factory. */ public LoadingInvocationUnit(ValueFactory valueFactory) { this(valueFactory, true, true, true); } /** * Creates a new LoadingInvocationUnit with the given value factory, for * loading the specified values. */ public LoadingInvocationUnit(ValueFactory valueFactory, boolean loadFieldValues, boolean loadMethodParameterValues, boolean loadMethodReturnValues) { super(valueFactory); this.loadFieldValues = loadFieldValues; this.loadMethodParameterValues = loadMethodParameterValues; this.loadMethodReturnValues = loadMethodReturnValues; } // Implementations for BasicInvocationUnit. @Override public Value getFieldClassValue(Clazz clazz, FieldrefConstant refConstant, String type) { if (loadFieldValues) { // Do we know this field? Field referencedMember = refConstant.referencedField; if (referencedMember != null) { // Retrieve the stored field class value. ReferenceValue value = StoringInvocationUnit.getFieldClassValue(referencedMember); if (value != null) { return value; } } } return super.getFieldClassValue(clazz, refConstant, type); } @Override public Value getFieldValue(Clazz clazz, FieldrefConstant refConstant, String type) { if (loadFieldValues) { // Do we know this field? Field referencedMember = refConstant.referencedField; if (referencedMember != null) { // Retrieve the stored field value. Value value = StoringInvocationUnit.getFieldValue(referencedMember); if (value != null) { return value; } } } return super.getFieldValue(clazz, refConstant, type); } @Override public Value getMethodParameterValue(Clazz clazz, Method method, int parameterIndex, String type, Clazz referencedClass) { if (loadMethodParameterValues) { // Retrieve the stored method parameter value. Value value = StoringInvocationUnit.getMethodParameterValue(method, parameterIndex); if (value != null) { return value; } } return super.getMethodParameterValue(clazz, method, parameterIndex, type, referencedClass); } @Override public Value getMethodReturnValue(Clazz clazz, AnyMethodrefConstant refConstant, String type) { if (loadMethodReturnValues) { // Do we know this method? Method referencedMember = refConstant.referencedMethod; if (referencedMember != null) { // Retrieve the stored method return value. Value value = StoringInvocationUnit.getMethodReturnValue(referencedMember); if (value != null) { return value; } } } return super.getMethodReturnValue(clazz, refConstant, type); } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/ParameterTracingInvocationUnit.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.evaluation; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.util.ClassUtil; import proguard.evaluation.*; import proguard.evaluation.value.*; import proguard.optimize.info.ParameterEscapeMarker; /** * This InvocationUnit tags reference values like * {@link ReferenceTracingInvocationUnit}, but adds detail to return values * from invoked methods. * * @see ReferenceTracingInvocationUnit * @see TracedReferenceValue * @see InstructionOffsetValue * @author Eric Lafortune */ public class ParameterTracingInvocationUnit extends ReferenceTracingInvocationUnit { private static final Logger logger = LogManager.getLogger(ParameterTracingInvocationUnit.class); private Value[] parameters = new Value[256]; /** * Creates a new ParameterTracingInvocationUnit that attaches trace * values specifying the parameter index to each parameter. * @param invocationUnit the invocation unit to which invocations will * be delegated. */ public ParameterTracingInvocationUnit(SimplifiedInvocationUnit invocationUnit) { super(invocationUnit); } // Implementations for SimplifiedInvocationUnit. @Override public void setMethodParameterValue(Clazz clazz, AnyMethodrefConstant refConstant, int parameterIndex, Value value) { super.setMethodParameterValue(clazz, refConstant, parameterIndex, value); parameters[parameterIndex] = value; } @Override public Value getMethodReturnValue(Clazz clazz, AnyMethodrefConstant refConstant, String type) { Value returnValue = super.getMethodReturnValue(clazz, refConstant, type); // We only need to worry about reference values. if (returnValue.computationalType() != Value.TYPE_REFERENCE) { return returnValue; } Method referencedMethod = refConstant.referencedMethod; if (referencedMethod != null) { // Start figuring out which trace value to attach to the return value. int offset = ((TracedReferenceValue)returnValue).getTraceValue().instructionOffsetValue().instructionOffset(0); // The trace value might be any external value or just a new instance. InstructionOffsetValue traceValue = ParameterEscapeMarker.returnsExternalValues(referencedMethod) ? new InstructionOffsetValue(offset | InstructionOffsetValue.FIELD_VALUE) : ParameterEscapeMarker.returnsNewInstances(referencedMethod) ? new InstructionOffsetValue(offset | InstructionOffsetValue.NEW_INSTANCE) : null; long returnedParameters = ParameterEscapeMarker.getReturnedParameters(referencedMethod); int parameterCount = ClassUtil.internalMethodParameterCount(refConstant.getType(clazz), isStatic); for (int parameterIndex = 0; parameterIndex < parameterCount; parameterIndex++) { if ((returnedParameters & (1L << parameterIndex)) != 0L) { Value parameterValue = parameters[parameterIndex]; if (parameterValue instanceof TracedReferenceValue) { TracedReferenceValue tracedParameterValue = (TracedReferenceValue)parameterValue; if (mayReturnType(refConstant.referencedClass, referencedMethod, tracedParameterValue)) { InstructionOffsetValue parameterTraceValue = tracedParameterValue.getTraceValue().instructionOffsetValue(); traceValue = traceValue == null ? parameterTraceValue : traceValue.generalize(parameterTraceValue); } } } } logger.debug("ParameterTracingInvocationUnit.getMethodReturnValue: calling [{}.{}{} ] returns [{} {}]", refConstant.getClassName(clazz), refConstant.getName(clazz), refConstant.getType(clazz), traceValue, returnValue ); // Did we find more detailed information on the return value? // We should, unless the return value is always null. if (traceValue != null) { return trace(returnValue, traceValue); } } return returnValue; } // Small utility methods. /** * Returns whether the given method may return the given type of reference * value */ private boolean mayReturnType(Clazz clazz, Method method, ReferenceValue referenceValue) { String returnType = ClassUtil.internalMethodReturnType(method.getDescriptor(clazz)); Clazz[] referencedClasses = method instanceof ProgramMethod ? ((ProgramMethod)method).referencedClasses : ((LibraryMethod)method).referencedClasses; Clazz referencedClass = referencedClasses == null || !ClassUtil.isInternalClassType(returnType) ? null : referencedClasses[referencedClasses.length - 1]; return referenceValue.instanceOf(returnType, referencedClass) != Value.NEVER; } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/SimpleEnumArrayPropagator.java ================================================ /* * 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.evaluation; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.visitor.*; import proguard.evaluation.value.*; import proguard.optimize.OptimizationInfoMemberFilter; import proguard.optimize.info.*; /** * This ClassVisitor propagates the value of the $VALUES field to the values() * method in the simple enum classes that it visits. * * @see SimpleEnumMarker * @author Eric Lafortune */ public class SimpleEnumArrayPropagator implements ClassVisitor, MemberVisitor { private static final Logger logger = LogManager.getLogger(SimpleEnumArrayPropagator.class); private final MemberVisitor fieldArrayFinder = new MemberDescriptorFilter("[I", this); private final MemberVisitor methodArrayPropagator = new OptimizationInfoMemberFilter( new MemberDescriptorFilter("()[I", this)); private final ValueFactory valueFactory = new ParticularValueFactory(); private Value array; // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Find the array of the "int[] $VALUES" field. array = null; programClass.fieldsAccept(fieldArrayFinder); if (array != null) { // Update the return value of the "int[] values()" method. programClass.methodsAccept(methodArrayPropagator); } } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { array = StoringInvocationUnit.getFieldValue(programField); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Set the array value with the found array length. We can't use // the original array, because its elements might get overwritten. Value propagatedArray = valueFactory.createArrayReferenceValue("I", null, array.referenceValue().arrayLength(valueFactory)); logger.debug("SimpleEnumArrayPropagator: [{}.{}{}]: propagating [{}] as return value", programClass.getName(), programMethod.getName(programClass), programMethod.getDescriptor(programClass), propagatedArray ); setMethodReturnValue(programMethod, propagatedArray); } // Small utility methods. private static void setMethodReturnValue(Method method, Value value) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setReturnValue( value); } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/SimpleEnumClassChecker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.evaluation; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.AccessConstants; import proguard.classfile.Clazz; import proguard.classfile.Member; import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.MemberAccessFilter; import proguard.classfile.visitor.MemberToClassVisitor; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.OptimizationInfoClassFilter; import proguard.optimize.info.SimpleEnumMarker; import java.util.Collections; import java.util.Set; import static proguard.classfile.ClassConstants.METHOD_NAME_INIT; import static proguard.classfile.ClassConstants.METHOD_TYPE_INIT_ENUM; /** * This ClassVisitor marks all program classes that it visits as simple enums, * if their methods qualify. * * @author Eric Lafortune */ public class SimpleEnumClassChecker implements ClassVisitor { private static final Logger logger = LogManager.getLogger(SimpleEnumClassChecker.class); private final ClassVisitor simpleEnumMarker = new OptimizationInfoClassFilter( new SimpleEnumMarker(true)); private final MemberVisitor instanceMemberChecker = new MemberAccessFilter(0, AccessConstants.STATIC, new MemberToClassVisitor( new SimpleEnumMarker(false))); // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Does the class have the simple enum constructor? Method simpleEnumConstructor = programClass.findMethod(METHOD_NAME_INIT, METHOD_TYPE_INIT_ENUM); if (simpleEnumConstructor == null) { return; } logger.debug("SimpleEnumClassChecker: [{}] is a candidate simple enum, without extra fields", programClass.getName()); // Mark it. simpleEnumMarker.visitProgramClass(programClass); // Unmark it again if it has any instance fields // or methods. programClass.fieldsAccept(instanceMemberChecker); programClass.methodsAccept( new MemberCollectionFilter( // The constructor should not be taken into account here // because it is handled by the SimpleEnumClassSimplifier. Collections.singleton(simpleEnumConstructor), null, instanceMemberChecker) ); } // TODO: replace usage with version in ProGuardCORE when available. private static class MemberCollectionFilter implements MemberVisitor { private final Set members; private final MemberVisitor acceptedVisitor; private final MemberVisitor rejectedVisitor; /** * Creates a new MemberCollectionFilter. * * @param members the members collection to be searched in. * @param acceptedVisitor this visitor will be called for members that are * present in the member collection. */ public MemberCollectionFilter(Set members, MemberVisitor acceptedVisitor) { this(members, acceptedVisitor, null); } /** * Creates a new MemberCollectionFilter. * * @param members the member collection to be searched in. * @param acceptedVisitor this visitor will be called for members that are * present in the member collection. * @param rejectedVisitor this visitor will be called otherwise. */ public MemberCollectionFilter(Set members, MemberVisitor acceptedVisitor, MemberVisitor rejectedVisitor) { this.members = members; this.acceptedVisitor = acceptedVisitor; this.rejectedVisitor = rejectedVisitor; } // Implementations for MemberVisitor. public void visitAnyMember(Clazz clazz, Member member) { MemberVisitor delegateVisitor = delegateVisitor(member); if (delegateVisitor != null) { member.accept(clazz, delegateVisitor); } } // Small utility methods. private MemberVisitor delegateVisitor(Member member) { return (members.contains(member)) ? acceptedVisitor : rejectedVisitor; } } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/SimpleEnumClassSimplifier.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.evaluation; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.visitor.*; import proguard.optimize.info.SimpleEnumMarker; /** * This ClassVisitor simplifies the classes that it visits to simple enums. * * @see SimpleEnumMarker * @see MemberReferenceFixer * @author Eric Lafortune */ public class SimpleEnumClassSimplifier implements ClassVisitor, AttributeVisitor { private static final Logger logger = LogManager.getLogger(SimpleEnumClassSimplifier.class); private static final int ENUM_CLASS_NAME = InstructionSequenceReplacer.A; private static final int ENUM_TYPE_NAME = InstructionSequenceReplacer.B; private static final int ENUM_CONSTANT_NAME = InstructionSequenceReplacer.X; private static final int ENUM_CONSTANT_ORDINAL = InstructionSequenceReplacer.Y; private static final int ENUM_CONSTANT_FIELD_NAME = InstructionSequenceReplacer.Z; private static final int STRING_ENUM_CONSTANT_NAME = 0; private static final int METHOD_ENUM_INIT = 1; private static final int FIELD_ENUM_CONSTANT = 2; private static final int CLASS_ENUM = 3; private static final int NAME_AND_TYPE_ENUM_INIT = 4; private static final int NAME_AND_TYPE_ENUM_CONSTANT = 5; private static final int UTF8_INIT = 6; private static final int UTF8_STRING_I = 7; private static final Constant[] CONSTANTS = new Constant[] { new StringConstant(ENUM_CONSTANT_NAME, null, null), new MethodrefConstant(CLASS_ENUM, NAME_AND_TYPE_ENUM_INIT, null, null), new FieldrefConstant( CLASS_ENUM, NAME_AND_TYPE_ENUM_CONSTANT, null, null), new ClassConstant(ENUM_CLASS_NAME, null), new NameAndTypeConstant(UTF8_INIT, UTF8_STRING_I), new NameAndTypeConstant(ENUM_CONSTANT_FIELD_NAME, ENUM_TYPE_NAME), new Utf8Constant(ClassConstants.METHOD_NAME_INIT), new Utf8Constant(ClassConstants.METHOD_TYPE_INIT_ENUM), }; private static final Instruction[][][] INSTRUCTION_SEQUENCES = new Instruction[][][] { { // Replace new Enum("name", constant) // by constant + 1. { new ConstantInstruction(Instruction.OP_NEW, CLASS_ENUM), new SimpleInstruction(Instruction.OP_DUP), new ConstantInstruction(Instruction.OP_LDC, STRING_ENUM_CONSTANT_NAME), new SimpleInstruction(Instruction.OP_ICONST_0, ENUM_CONSTANT_ORDINAL), new ConstantInstruction(Instruction.OP_INVOKESPECIAL, METHOD_ENUM_INIT), }, { new SimpleInstruction(Instruction.OP_SIPUSH, ENUM_CONSTANT_ORDINAL), new SimpleInstruction(Instruction.OP_ICONST_1), new SimpleInstruction(Instruction.OP_IADD), } }, { // The name constants may have been encrypted. // Replace (..., constant) // by (..., 0); pop; constant + 1. { new SimpleInstruction(Instruction.OP_ICONST_0, ENUM_CONSTANT_ORDINAL), new ConstantInstruction(Instruction.OP_INVOKESPECIAL, METHOD_ENUM_INIT), }, { new SimpleInstruction(Instruction.OP_ICONST_0), new ConstantInstruction(Instruction.OP_INVOKESPECIAL, METHOD_ENUM_INIT), new SimpleInstruction(Instruction.OP_POP), new SimpleInstruction(Instruction.OP_SIPUSH, ENUM_CONSTANT_ORDINAL), new SimpleInstruction(Instruction.OP_ICONST_1), new SimpleInstruction(Instruction.OP_IADD), } }, { // The code might be obfuscated, pushing constant and invokespecial // might not come directly after each other, e.g. when obfuscated by Arxan. // Replace (..., constant) // by dup_x2; pop; (..., 0); swap; pop; constant + 1. { new ConstantInstruction(Instruction.OP_INVOKESPECIAL, METHOD_ENUM_INIT), }, { new SimpleInstruction(Instruction.OP_DUP_X2), new SimpleInstruction(Instruction.OP_POP), new SimpleInstruction(Instruction.OP_ICONST_0), new ConstantInstruction(Instruction.OP_INVOKESPECIAL, METHOD_ENUM_INIT), new SimpleInstruction(Instruction.OP_SWAP), new SimpleInstruction(Instruction.OP_POP), new SimpleInstruction(Instruction.OP_ICONST_1), new SimpleInstruction(Instruction.OP_IADD), } }, }; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true); private final InstructionSequencesReplacer instructionSequenceReplacer = new InstructionSequencesReplacer(CONSTANTS, INSTRUCTION_SEQUENCES, null, codeAttributeEditor); // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { logger.debug("SimpleEnumClassSimplifier: [{}]", programClass.getName()); // Unmark the class as an enum. programClass.u2accessFlags &= ~AccessConstants.ENUM; // Remove the valueOf method, if present. Method valueOfMethod = programClass.findMethod(ClassConstants.METHOD_NAME_VALUEOF, null); if (valueOfMethod != null) { new ClassEditor(programClass).removeMethod(valueOfMethod); } // Simplify the static initializer. programClass.methodAccept(ClassConstants.METHOD_NAME_CLINIT, ClassConstants.METHOD_TYPE_CLINIT, new AllAttributeVisitor(this)); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Set up the code attribute editor. codeAttributeEditor.reset(codeAttribute.u4codeLength); // Find the peephole changes. codeAttribute.instructionsAccept(clazz, method, instructionSequenceReplacer); // Apply the peephole changes. codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/SimpleEnumDescriptorSimplifier.java ================================================ /* * 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.evaluation; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.optimize.KeepMarker; import proguard.optimize.info.*; /** * This ClassVisitor simplifies the descriptors that contain simple enums in * the program classes that it visits. * * @see SimpleEnumMarker * @see MemberReferenceFixer * @author Eric Lafortune */ public class SimpleEnumDescriptorSimplifier implements ClassVisitor, ConstantVisitor, MemberVisitor, AttributeVisitor, LocalVariableInfoVisitor, LocalVariableTypeInfoVisitor { private static final Logger logger = LogManager.getLogger(SimpleEnumDescriptorSimplifier.class); private static final boolean DEBUG_EXTRA = false; // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { logger.debug("SimpleEnumDescriptorSimplifier: {}", programClass.getName()); // Simplify the class members. programClass.fieldsAccept(this); programClass.methodsAccept(this); // Simplify the attributes. //programClass.attributesAccept(this); // Simplify the simple enum array constants. programClass.constantPoolEntriesAccept(this); } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Does the constant refer to a simple enum type? Clazz referencedClass = stringConstant.referencedClass; if (isSimpleEnum(referencedClass)) { // Is it an array type? String name = stringConstant.getString(clazz); if (ClassUtil.isInternalArrayType(name)) { // Update the type. ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); String newName = simplifyDescriptor(name, referencedClass); stringConstant.u2stringIndex = constantPoolEditor.addUtf8Constant(newName); // Clear the referenced class. stringConstant.referencedClass = null; } } } public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { // Update the descriptor if it has any simple enum classes. String descriptor = invokeDynamicConstant.getType(clazz); String newDescriptor = simplifyDescriptor(descriptor, invokeDynamicConstant.referencedClasses); if (!descriptor.equals(newDescriptor)) { // Update the descriptor. ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); invokeDynamicConstant.u2nameAndTypeIndex = constantPoolEditor.addNameAndTypeConstant(invokeDynamicConstant.getName(clazz), newDescriptor); // Update the referenced classes. invokeDynamicConstant.referencedClasses = simplifyReferencedClasses(descriptor, invokeDynamicConstant.referencedClasses); } } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Does the constant refer to a simple enum type? Clazz referencedClass = classConstant.referencedClass; if (isSimpleEnum(referencedClass)) { // Is it an array type? String name = classConstant.getName(clazz); if (ClassUtil.isInternalArrayType(name)) { // Update the type. ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); String newName = simplifyDescriptor(name, referencedClass); classConstant.u2nameIndex = constantPoolEditor.addUtf8Constant(newName); // Clear the referenced class. classConstant.referencedClass = null; } } } public void visitMethodTypeConstant(Clazz clazz, MethodTypeConstant methodTypeConstant) { // Update the descriptor if it has any simple enum classes. String descriptor = methodTypeConstant.getType(clazz); String newDescriptor = simplifyDescriptor(descriptor, methodTypeConstant.referencedClasses); if (!descriptor.equals(newDescriptor)) { // Update the descriptor. ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass)clazz); methodTypeConstant.u2descriptorIndex = constantPoolEditor.addUtf8Constant(newDescriptor); // Update the referenced classes. methodTypeConstant.referencedClasses = simplifyReferencedClasses(descriptor, methodTypeConstant.referencedClasses); } } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Update the descriptor if it has a simple enum class. String descriptor = programField.getDescriptor(programClass); String newDescriptor = simplifyDescriptor(descriptor, programField.referencedClass); if (!descriptor.equals(newDescriptor)) { String name = programField.getName(programClass); String newName = name + TypeConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode())); logger.debug("SimpleEnumDescriptorSimplifier: [{}.{} {}] -> [{} {}]", programClass.getName(), name, descriptor, newName, newDescriptor ); ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass); // Update the name. programField.u2nameIndex = constantPoolEditor.addUtf8Constant(newName); // Update the descriptor itself. programField.u2descriptorIndex = constantPoolEditor.addUtf8Constant(newDescriptor); // Clear the referenced class. programField.referencedClass = null; // Clear the enum flag. programField.u2accessFlags &= ~AccessConstants.ENUM; // Clear the field value. if (!KeepMarker.isKept(programField)) { ProgramFieldOptimizationInfo.getProgramFieldOptimizationInfo(programField).resetValue(programClass, programField); } // Simplify the signature. programField.attributesAccept(programClass, this); } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // // Skip the valueOf method. // if (programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_VALUEOF)) // { // return; // } // Simplify the code, the signature, and the parameter annotations, // before simplifying the descriptor. programMethod.attributesAccept(programClass, this); // Update the descriptor if it has any simple enum classes. String descriptor = programMethod.getDescriptor(programClass); String newDescriptor = simplifyDescriptor(descriptor, programMethod.referencedClasses); if (!descriptor.equals(newDescriptor)) { String name = programMethod.getName(programClass); String newName = name; // Append a code, if the method isn't a class instance initializer. if (!name.equals(ClassConstants.METHOD_NAME_INIT)) { newName += TypeConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode())); } logger.debug("SimpleEnumDescriptorSimplifier: [{}.{}{}] -> [{}{}]", programClass.getName(), name, descriptor, newName, newDescriptor ); ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass); // Update the name, if necessary. if (!newName.equals(name)) { programMethod.u2nameIndex = constantPoolEditor.addUtf8Constant(newName); } // Update the descriptor itself. programMethod.u2descriptorIndex = constantPoolEditor.addUtf8Constant(newDescriptor); // Update the referenced classes. programMethod.referencedClasses = simplifyReferencedClasses(descriptor, programMethod.referencedClasses); } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Simplify the local variable descriptors. codeAttribute.attributesAccept(clazz, method, this); } public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { // Change the references of the local variables. localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { // Change the references of the local variables. localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) { // Compute the new signature. String signature = signatureAttribute.getSignature(clazz); String newSignature = simplifyDescriptor(signature, signatureAttribute.referencedClasses); if (!signature.equals(newSignature)) { // Update the signature. signatureAttribute.u2signatureIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); // Update the referenced classes. signatureAttribute.referencedClasses = simplifyReferencedClasses(signature, signatureAttribute.referencedClasses); } } // Implementations for LocalVariableInfoVisitor. public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) { // Update the descriptor if it has a simple enum class. String descriptor = localVariableInfo.getDescriptor(clazz); String newDescriptor = simplifyDescriptor(descriptor, localVariableInfo.referencedClass); if (!descriptor.equals(newDescriptor)) { // Update the descriptor. localVariableInfo.u2descriptorIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor); // Clear the referenced class. localVariableInfo.referencedClass = null; } } // Implementations for LocalVariableTypeInfoVisitor. public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) { // We're only looking at the base type for now. if (localVariableTypeInfo.referencedClasses != null && localVariableTypeInfo.referencedClasses.length > 0) { // Update the signature if it has any simple enum classes. String signature = localVariableTypeInfo.getSignature(clazz); String newSignature = simplifyDescriptor(signature, localVariableTypeInfo.referencedClasses[0]); if (!signature.equals(newSignature)) { // Update the signature. localVariableTypeInfo.u2signatureIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); // Clear the referenced class. localVariableTypeInfo.referencedClasses[0] = null; } } } // Small utility methods. /** * Returns the descriptor with simplified enum type. */ private String simplifyDescriptor(String descriptor, Clazz referencedClass) { return isSimpleEnum(referencedClass) ? descriptor.substring(0, ClassUtil.internalArrayTypeDimensionCount(descriptor)) + TypeConstants.INT : descriptor; } /** * Returns the descriptor with simplified enum types. */ private String simplifyDescriptor(String descriptor, Clazz[] referencedClasses) { if (referencedClasses != null) { logger.trace(" Before: [{}]", descriptor); InternalTypeEnumeration typeEnumeration = new InternalTypeEnumeration(descriptor); int referencedClassIndex = 0; StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length()); // Go over the formal type parameters. if (typeEnumeration.hasFormalTypeParameters()) { // Consider the classes referenced by this formal type // parameter. String type = typeEnumeration.formalTypeParameters(); DescriptorClassEnumeration classEnumeration = new DescriptorClassEnumeration(type); newDescriptorBuffer.append(classEnumeration.nextFluff()); // Replace any simple enum types. while (classEnumeration.hasMoreClassNames()) { // Get the class. String className = classEnumeration.nextClassName(); Clazz referencedClass = referencedClasses[referencedClassIndex++]; // Is this class a simple enum type? if (isSimpleEnum(referencedClass)) { // Let's replace it by java.lang.Integer. className = ClassConstants.NAME_JAVA_LANG_INTEGER; } newDescriptorBuffer.append(className); newDescriptorBuffer.append(classEnumeration.nextFluff()); } } if (typeEnumeration.isMethodSignature()) { newDescriptorBuffer.append(TypeConstants.METHOD_ARGUMENTS_OPEN); } // Go over the main types (class types or parameter types). while (typeEnumeration.hasMoreTypes()) { // Consider the classes referenced by this parameter type. String type = typeEnumeration.nextType(); DescriptorClassEnumeration classEnumeration = new DescriptorClassEnumeration(type); String firstFluff = classEnumeration.nextFluff(); if (classEnumeration.hasMoreClassNames()) { // Get the first class. String firstClassName = classEnumeration.nextClassName(); Clazz firstReferencedClass = referencedClasses[referencedClassIndex++]; // Is the first class a simple enum type? if (isSimpleEnum(firstReferencedClass)) { // Replace it by a primitive int, with any array // prefix. newDescriptorBuffer.append(type.substring(0, ClassUtil.internalArrayTypeDimensionCount(type))); newDescriptorBuffer.append(TypeConstants.INT); // Skip any other classes of this type. classEnumeration.nextFluff(); while (classEnumeration.hasMoreClassNames()) { classEnumeration.nextClassName(); classEnumeration.nextFluff(); referencedClassIndex++; } } else { newDescriptorBuffer.append(firstFluff); newDescriptorBuffer.append(firstClassName); newDescriptorBuffer.append(classEnumeration.nextFluff()); // Replace any other simple enum types. while (classEnumeration.hasMoreClassNames()) { // Get the class. String className = classEnumeration.nextClassName(); Clazz referencedClass = referencedClasses[referencedClassIndex++]; // Is this class a simple enum type? if (isSimpleEnum(referencedClass)) { // Let's replace it by java.lang.Integer. className = ClassConstants.NAME_JAVA_LANG_INTEGER; } newDescriptorBuffer.append(className); newDescriptorBuffer.append(classEnumeration.nextFluff()); } } } else { newDescriptorBuffer.append(firstFluff); } } if (typeEnumeration.isMethodSignature()) { newDescriptorBuffer.append(TypeConstants.METHOD_ARGUMENTS_CLOSE); // Consider the classes referenced by the return type. String type = typeEnumeration.returnType(); DescriptorClassEnumeration classEnumeration = new DescriptorClassEnumeration(type); String firstFluff = classEnumeration.nextFluff(); if (classEnumeration.hasMoreClassNames()) { // Get the first class. String firstClassName = classEnumeration.nextClassName(); Clazz firstReferencedClass = referencedClasses[referencedClassIndex++]; // Is the first class a simple enum type? if (isSimpleEnum(firstReferencedClass)) { // Replace it by a primitive int, with any array // prefix. newDescriptorBuffer.append(type.substring(0, ClassUtil.internalArrayTypeDimensionCount(type))); newDescriptorBuffer.append(TypeConstants.INT); } else { newDescriptorBuffer.append(firstFluff); newDescriptorBuffer.append(firstClassName); newDescriptorBuffer.append(classEnumeration.nextFluff()); // Replace any other simple enum types. while (classEnumeration.hasMoreClassNames()) { // Get the class. String className = classEnumeration.nextClassName(); Clazz referencedClass = referencedClasses[referencedClassIndex++]; // Is this class a simple enum type? if (isSimpleEnum(referencedClass)) { // Let's replace it by java.lang.Integer. className = ClassConstants.NAME_JAVA_LANG_INTEGER; } newDescriptorBuffer.append(className); newDescriptorBuffer.append(classEnumeration.nextFluff()); } } } else { newDescriptorBuffer.append(firstFluff); } } descriptor = newDescriptorBuffer.toString(); logger.trace(" After: [{}]", descriptor); } return descriptor; } /** * Returns the simplified and shrunk array of referenced classes for the * given descriptor. */ private Clazz[] simplifyReferencedClasses(String descriptor, Clazz[] referencedClasses) { if (referencedClasses != null) { if (logger.getLevel().isLessSpecificThan(Level.TRACE)) { StringBuilder traceMessage = new StringBuilder(" Referenced before:"); for (int index = 0; index < referencedClasses.length; index++) { traceMessage.append(String.format(" [%s]", referencedClasses[index] == null ? null : referencedClasses[index].getName())); } logger.trace(traceMessage); } InternalTypeEnumeration typeEnumeration = new InternalTypeEnumeration(descriptor); int referencedClassIndex = 0; int newReferencedClassIndex = 0; // Go over the formal type parameters. if (typeEnumeration.hasFormalTypeParameters()) { // Consider the classes referenced by this formal type // parameter. String type = typeEnumeration.formalTypeParameters(); DescriptorClassEnumeration classEnumeration = new DescriptorClassEnumeration(type); classEnumeration.nextFluff(); // Replace any simple enum types. while (classEnumeration.hasMoreClassNames()) { // Get the class. classEnumeration.nextClassName(); classEnumeration.nextFluff(); Clazz referencedClass = referencedClasses[referencedClassIndex++]; // Clear the referenced class if it is a simple // enum type (now java.lang.Integer). referencedClasses[newReferencedClassIndex++] = isSimpleEnum(referencedClass) ? null : referencedClass; } } // Go over the main types (class types or parameter types). while (typeEnumeration.hasMoreTypes()) { // Consider the classes referenced by this parameter type. String type = typeEnumeration.nextType(); DescriptorClassEnumeration classEnumeration = new DescriptorClassEnumeration(type); classEnumeration.nextFluff(); if (classEnumeration.hasMoreClassNames()) { // Get the first class. classEnumeration.nextClassName(); classEnumeration.nextFluff(); Clazz firstReferencedClass = referencedClasses[referencedClassIndex++]; // Is the first class a simple enum type? if (isSimpleEnum(firstReferencedClass)) { // Replace it by a primitive int. // Skip any other classes of this type. classEnumeration.nextFluff(); while (classEnumeration.hasMoreClassNames()) { classEnumeration.nextClassName(); classEnumeration.nextFluff(); referencedClassIndex++; } } else { referencedClasses[newReferencedClassIndex++] = firstReferencedClass; // Replace any other simple enum types. while (classEnumeration.hasMoreClassNames()) { // Get the class. classEnumeration.nextClassName(); classEnumeration.nextFluff(); Clazz referencedClass = referencedClasses[referencedClassIndex++]; // Clear the referenced class if it is a simple // enum type (now java.lang.Integer). referencedClasses[newReferencedClassIndex++] = isSimpleEnum(referencedClass) ? null : referencedClass; } } } } if (typeEnumeration.isMethodSignature()) { // Consider the classes referenced by the return type. String type = typeEnumeration.returnType(); DescriptorClassEnumeration classEnumeration = new DescriptorClassEnumeration(type); classEnumeration.nextFluff(); if (classEnumeration.hasMoreClassNames()) { // Get the first class. classEnumeration.nextClassName(); classEnumeration.nextFluff(); Clazz firstReferencedClass = referencedClasses[referencedClassIndex++]; // Is the first class a simple enum type? if (isSimpleEnum(firstReferencedClass)) { // Replace it by a primitive int. // Clear all remaining referenced classes. } else { referencedClasses[newReferencedClassIndex++] = firstReferencedClass; // Replace any other simple enum types. while (classEnumeration.hasMoreClassNames()) { // Get the class. classEnumeration.nextClassName(); classEnumeration.nextFluff(); Clazz referencedClass = referencedClasses[referencedClassIndex++]; // Clear the referenced class if it is a simple // enum type (now java.lang.Integer). referencedClasses[newReferencedClassIndex++] = isSimpleEnum(referencedClass) ? null : referencedClass; } } } } // Shrink the array to the proper size. if (newReferencedClassIndex == 0) { referencedClasses = null; } else if (newReferencedClassIndex < referencedClassIndex) { Clazz[] newReferencedClasses = new Clazz[newReferencedClassIndex]; System.arraycopy(referencedClasses, 0, newReferencedClasses, 0, newReferencedClassIndex); referencedClasses = newReferencedClasses; if (logger.getLevel().isLessSpecificThan(Level.TRACE)) { StringBuilder traceMessage = new StringBuilder(" Referenced after: "); for (int index = 0; index < referencedClasses.length; index++) { traceMessage.append(String.format(" [%s]", referencedClasses[index] == null ? null : referencedClasses[index].getName())); } logger.trace(traceMessage); } } } return referencedClasses; } /** * Returns whether the given class is not null and a simple enum class. */ private boolean isSimpleEnum(Clazz clazz) { return clazz != null && SimpleEnumMarker.isSimpleEnum(clazz); } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/SimpleEnumUseChecker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.evaluation; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.evaluation.*; import proguard.evaluation.value.*; import proguard.optimize.OptimizationInfoClassFilter; import proguard.optimize.info.SimpleEnumMarker; /** * This ClassVisitor marks enums that can't be simplified due to the way they * are used in the classes that it visits. * * @see SimpleEnumMarker * @author Eric Lafortune */ public class SimpleEnumUseChecker implements ClassVisitor, MemberVisitor, AttributeVisitor, BootstrapMethodInfoVisitor, ConstantVisitor, InstructionVisitor, ParameterVisitor { private static final Logger logger = LogManager.getLogger(SimpleEnumUseChecker.class); private final PartialEvaluator partialEvaluator; private final MemberVisitor methodCodeChecker = new AllAttributeVisitor(this); private final ConstantVisitor invokedMethodChecker = new ReferencedMemberVisitor(this); private final ConstantVisitor parameterChecker = new ReferencedMemberVisitor(new AllParameterVisitor(false, this)); private final ClassVisitor complexEnumMarker = new OptimizationInfoClassFilter(new SimpleEnumMarker(false)); private final ReferencedClassVisitor referencedComplexEnumMarker = new ReferencedClassVisitor(complexEnumMarker); // Fields acting as parameters and return values for the visitor methods. private int invocationOffset; /** * Creates a new SimpleEnumUseSimplifier. */ public SimpleEnumUseChecker() { this(new PartialEvaluator(new TypedReferenceValueFactory())); } /** * Creates a new SimpleEnumUseChecker. * @param partialEvaluator the partial evaluator that will execute the code * and provide information about the results. */ public SimpleEnumUseChecker(PartialEvaluator partialEvaluator) { this.partialEvaluator = partialEvaluator; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Unmark the simple enum classes in bootstrap methods attributes. programClass.attributesAccept(this); if ((programClass.getAccessFlags() & AccessConstants.ANNOTATION) != 0) { // Unmark the simple enum classes in annotations. programClass.methodsAccept(referencedComplexEnumMarker); } else { // Unmark the simple enum classes that are used in a complex way. programClass.methodsAccept(methodCodeChecker); } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { // Unmark the simple enum classes in all bootstrap methods. bootstrapMethodsAttribute.bootstrapMethodEntriesAccept(clazz, this); } public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Evaluate the method. partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); int codeLength = codeAttribute.u4codeLength; // Check all traced instructions. for (int offset = 0; offset < codeLength; offset++) { if (partialEvaluator.isTraced(offset)) { Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); instruction.accept(clazz, method, codeAttribute, offset, this); // Check generalized stacks and variables at branch targets. if (partialEvaluator.isBranchOrExceptionTarget(offset)) { checkMixedStackEntriesBefore(offset); checkMixedVariablesBefore(offset); } } } } // Implementations for BootstrapMethodInfoVisitor. public void visitBootstrapMethodInfo(Clazz clazz, BootstrapMethodInfo bootstrapMethodInfo) { // Unmark the simple enum classes referenced in the method handle. bootstrapMethodInfo.methodHandleAccept(clazz, this); // Unmark the simple enum classes referenced in the method arguments. bootstrapMethodInfo.methodArgumentsAccept(clazz, this); } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Unmark any simple enum class referenced in the string constant. stringConstant.referencedClassAccept(complexEnumMarker); } public void visitMethodHandleConstant(Clazz clazz, MethodHandleConstant methodHandleConstant) { // Unmark the simple enum classes referenced in the method handle // (through a reference constant). methodHandleConstant.referenceAccept(clazz, this); } public void visitMethodTypeConstant(Clazz clazz, MethodTypeConstant methodTypeConstant) { // Unmark the simple enum classes referenced in the method type constant. methodTypeConstant.referencedClassesAccept(referencedComplexEnumMarker); } public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { // Unmark the simple enum classes referenced in the reference. refConstant.referencedClassAccept(referencedComplexEnumMarker); } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Unmark any simple enum class referenced in the class constant. classConstant.referencedClassAccept(complexEnumMarker); } // Implementations for InstructionVisitor. public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { switch (simpleInstruction.opcode) { case Instruction.OP_AASTORE: { // Check if the instruction is storing a simple enum in a // more general array. if (!isPoppingSimpleEnumType(offset, 2)) { if (isPoppingSimpleEnumType(offset)) { logger.debug("SimpleEnumUseChecker: [{}.{}{}] stores enum [{}] in more general array [{}]", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType(), partialEvaluator.getStackBefore(offset).getTop(2).referenceValue().getType()); } markPoppedComplexEnumType(offset); } break; } case Instruction.OP_ARETURN: { // Check if the instruction is returning a simple enum as a // more general type. if (!isReturningSimpleEnumType(clazz, method)) { if (isPoppingSimpleEnumType(offset)) { logger.debug("SimpleEnumUseChecker: [{}.{}{}] returns enum [{}] as more general type", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType()); } markPoppedComplexEnumType(offset); } break; } case Instruction.OP_MONITORENTER: case Instruction.OP_MONITOREXIT: { // Make sure the popped type is not a simple enum type. if (isPoppingSimpleEnumType(offset)) { logger.debug("SimpleEnumUseChecker: [{}.{}{}] uses enum [{}] as monitor", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType() ); } markPoppedComplexEnumType(offset); break; } } } public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_PUTSTATIC: case Instruction.OP_PUTFIELD: { // Check if the instruction is generalizing a simple enum to a // different type. invocationOffset = offset; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, parameterChecker); break; } case Instruction.OP_INVOKEVIRTUAL: { // Check if the instruction is calling a simple enum. String invokedMethodName = clazz.getRefName(constantInstruction.constantIndex); String invokedMethodType = clazz.getRefType(constantInstruction.constantIndex); int stackEntryIndex = ClassUtil.internalMethodParameterSize(invokedMethodType); if (isPoppingSimpleEnumType(offset, stackEntryIndex) && !isSupportedMethod(invokedMethodName, invokedMethodType)) { logger.debug("SimpleEnumUseChecker: [{}.{}{}] calls [{}.{}]", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue().getType(), invokedMethodName ); markPoppedComplexEnumType(offset, stackEntryIndex); } // Check if any of the parameters is generalizing a simple // enum to a different type. invocationOffset = offset; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, parameterChecker); break; } case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: case Instruction.OP_INVOKEINTERFACE: { // Check if it is calling a method that we can't simplify. clazz.constantPoolEntryAccept(constantInstruction.constantIndex, invokedMethodChecker); // Check if any of the parameters is generalizing a simple // enum to a different type. invocationOffset = offset; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, parameterChecker); break; } case Instruction.OP_CHECKCAST: case Instruction.OP_INSTANCEOF: { // Check if the instruction is popping a different type. if (!isPoppingExpectedType(offset, clazz, constantInstruction.constantIndex)) { if (isPoppingSimpleEnumType(offset)) { logger.debug("SimpleEnumUseChecker: [{}.{}{}] is casting or checking [{}] as [{}]", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType(), clazz.getClassName(constantInstruction.constantIndex) ); } // Make sure the popped type is not a simple enum type. markPoppedComplexEnumType(offset); // Make sure the checked type is not a simple enum type. // Casts in values() and valueOf(String) are ok. if (constantInstruction.opcode != Instruction.OP_CHECKCAST || !isSimpleEnum(clazz) || (method.getAccessFlags() & AccessConstants.STATIC) == 0 || !isMethodSkippedForCheckcast(method.getName(clazz), method.getDescriptor(clazz))) { if (isSimpleEnum(((ClassConstant)((ProgramClass)clazz).getConstant(constantInstruction.constantIndex)).referencedClass)) { logger.debug("SimpleEnumUseChecker: [{}.{}{}] is casting or checking [{}] as [{}]", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType(), clazz.getClassName(constantInstruction.constantIndex) ); } markConstantComplexEnumType(clazz, constantInstruction.constantIndex); } } break; } } } public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { switch (branchInstruction.opcode) { case Instruction.OP_IFACMPEQ: case Instruction.OP_IFACMPNE: { // Check if the instruction is comparing different types. if (!isPoppingIdenticalTypes(offset, 0, 1)) { if (isPoppingSimpleEnumType(offset, 0)) { logger.debug("SimpleEnumUseChecker: [{}.{}{}] compares [{}] to plain type", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType() ); } if (isPoppingSimpleEnumType(offset, 1)) { logger.debug("SimpleEnumUseChecker: [{}.{}{}] compares [{}] to plain type", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), partialEvaluator.getStackBefore(offset).getTop(1).referenceValue().getType() ); } // Make sure the first popped type is not a simple enum type. markPoppedComplexEnumType(offset, 0); // Make sure the second popped type is not a simple enum type. markPoppedComplexEnumType(offset, 1); } break; } } } public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction) { } // Implementations for MemberVisitor. public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (isSimpleEnum(programClass) && isUnsupportedMethod(programMethod.getName(programClass), programMethod.getDescriptor(programClass))) { logger.debug("SimpleEnumUseChecker: invocation of [{}.{}{}]", programClass.getName(), programMethod.getName(programClass), programMethod.getDescriptor(programClass) ); complexEnumMarker.visitProgramClass(programClass); } } // Implementations for ParameterVisitor. public void visitParameter(Clazz clazz, Member member, int parameterIndex, int parameterCount, int parameterOffset, int parameterSize, String parameterType, Clazz referencedClass) { // Check if the parameter is passing a simple enum as a more general // type. int stackEntryIndex = parameterSize - parameterOffset - 1; if (ClassUtil.isInternalClassType(parameterType) && !isPoppingExpectedType(invocationOffset, stackEntryIndex, parameterType)) { ReferenceValue poppedValue = partialEvaluator.getStackBefore(invocationOffset).getTop(stackEntryIndex).referenceValue(); if (isSimpleEnumType(poppedValue)) { logger.debug("SimpleEnumUseChecker: [{}] {}", poppedValue.getType(), member instanceof Field ? ("is stored as more general type ["+parameterType+"] in field ["+clazz.getName()+"."+member.getName(clazz)+"]") : ("is passed as more general argument #"+parameterIndex+" ["+parameterType+"] to ["+clazz.getName()+"."+member.getName(clazz)+"]")); } // Make sure the popped type is not a simple enum type. markPoppedComplexEnumType(invocationOffset, stackEntryIndex); } } // Small utility methods. /** * Returns whether the specified enum method is supported for simple enums. */ private boolean isSupportedMethod(String name, String type) { return name.equals(ClassConstants.METHOD_NAME_ORDINAL) && type.equals(ClassConstants.METHOD_TYPE_ORDINAL) || name.equals(ClassConstants.METHOD_NAME_CLONE) && type.equals(ClassConstants.METHOD_TYPE_CLONE); } /** * Returns whether the specified enum method is unsupported for simple enums. */ private boolean isUnsupportedMethod(String name, String type) { return name.equals(ClassConstants.METHOD_NAME_VALUEOF); } /** * Returns whether the specified enum method shall be skipped when * analyzing checkcast instructions. */ private boolean isMethodSkippedForCheckcast(String name, String type) { return name.equals(ClassConstants.METHOD_NAME_VALUEOF) || name.equals(ClassConstants.METHOD_NAME_VALUES); } /** * Unmarks simple enum classes that are mixed with incompatible reference * types in the stack before the given instruction offset. */ private void checkMixedStackEntriesBefore(int offset) { TracedStack stackBefore = partialEvaluator.getStackBefore(offset); // Check all stack entries. int stackSize = stackBefore.size(); for (int stackEntryIndex = 0; stackEntryIndex < stackSize; stackEntryIndex++) { // Check reference entries. Value stackEntry = stackBefore.getBottom(stackEntryIndex); if (stackEntry.computationalType() == Value.TYPE_REFERENCE) { // Check reference entries with multiple producers. InstructionOffsetValue producerOffsets = stackBefore.getBottomActualProducerValue(stackEntryIndex).instructionOffsetValue(); int producerCount = producerOffsets.instructionOffsetCount(); if (producerCount > 1) { // Is the consumed stack entry not a simple enum? ReferenceValue consumedStackEntry = stackEntry.referenceValue(); if (!isSimpleEnumType(consumedStackEntry)) { // Check all producers. for (int producerIndex = 0; producerIndex < producerCount; producerIndex++) { if (!producerOffsets.isExceptionHandler(producerIndex)) { int producerOffset = producerOffsets.instructionOffset(producerIndex); ReferenceValue producedValue = partialEvaluator.getStackAfter(producerOffset).getTop(0).referenceValue(); if (isSimpleEnumType(producedValue)) { logger.debug("SimpleEnumUseChecker: [{}] mixed with general type on stack", producedValue.getType()); } // Make sure the produced stack entry isn't a // simple enum either. markPushedComplexEnumType(producerOffset); } } } } } } } /** * Unmarks simple enum classes that are mixed with incompatible reference * types in the variables before the given instruction offset. */ private void checkMixedVariablesBefore(int offset) { TracedVariables variablesBefore = partialEvaluator.getVariablesBefore(offset); // Check all variables. int variablesSize = variablesBefore.size(); for (int variableIndex = 0; variableIndex < variablesSize; variableIndex++) { // Check reference variables. Value variable = variablesBefore.getValue(variableIndex); if (variable != null && variable.computationalType() == Value.TYPE_REFERENCE) { // Check reference variables with multiple producers. InstructionOffsetValue producerOffsets = variablesBefore.getProducerValue(variableIndex).instructionOffsetValue(); int producerCount = producerOffsets.instructionOffsetCount(); if (producerCount > 1) { // Is the consumed variable not a simple enum? ReferenceValue consumedVariable = variable.referenceValue(); if (!isSimpleEnumType(consumedVariable)) { // Check all producers. for (int producerIndex = 0; producerIndex < producerCount; producerIndex++) { if (!producerOffsets.isMethodParameter(producerIndex)) { int producerOffset = producerOffsets.instructionOffset(producerIndex); ReferenceValue producedValue = partialEvaluator.getVariablesAfter(producerOffset).getValue(variableIndex).referenceValue(); if (isSimpleEnumType(producedValue)) { logger.debug("SimpleEnumUseChecker: [{}] mixed with general type in variables", producedValue.getType()); } // Make sure the stored variable entry isn't a // simple enum either. markStoredComplexEnumType(producerOffset, variableIndex); } } } } } } } /** * Returns whether the instruction at the given offset is popping two * identical reference types. */ private boolean isPoppingIdenticalTypes(int offset, int stackEntryIndex1, int stackEntryIndex2) { TracedStack stackBefore = partialEvaluator.getStackBefore(offset); String type1 = stackBefore.getTop(stackEntryIndex1).referenceValue().getType(); String type2 = stackBefore.getTop(stackEntryIndex2).referenceValue().getType(); return type1 == null ? type2 == null : type1.equals(type2); } /** * Returns whether the instruction at the given offset is popping exactly * the reference type of the specified class constant. */ private boolean isPoppingExpectedType(int offset, Clazz clazz, int constantIndex) { return isPoppingExpectedType(offset, 0, clazz, constantIndex); } /** * Returns whether the instruction at the given offset is popping exactly * the reference type of the specified class constant. */ private boolean isPoppingExpectedType(int offset, int stackEntryIndex, Clazz clazz, int constantIndex) { return isPoppingExpectedType(offset, stackEntryIndex, ClassUtil.internalTypeFromClassType(clazz.getClassName(constantIndex))); } /** * Returns whether the instruction at the given offset is popping exactly * the given reference type. */ private boolean isPoppingExpectedType(int offset, int stackEntryIndex, String expectedType) { TracedStack stackBefore = partialEvaluator.getStackBefore(offset); String poppedType = stackBefore.getTop(stackEntryIndex).referenceValue().getType(); return expectedType.equals(poppedType); } /** * Returns whether the given method is returning a simple enum type. * This includes simple enum arrays. */ private boolean isReturningSimpleEnumType(Clazz clazz, Method method) { String descriptor = method.getDescriptor(clazz); String returnType = ClassUtil.internalMethodReturnType(descriptor); if (ClassUtil.isInternalClassType(returnType)) { Clazz[] referencedClasses = ((ProgramMethod)method).referencedClasses; if (referencedClasses != null) { Clazz referencedClass = referencedClasses[referencedClasses.length - 1]; return isSimpleEnum(referencedClass); } } return false; } /** * Returns whether the instruction at the given offset is popping a type * with a simple enum class. This includes simple enum arrays. */ private boolean isPoppingSimpleEnumType(int offset) { return isPoppingSimpleEnumType(offset, 0); } /** * Returns whether the instruction at the given offset is popping a type * with a simple enum class. This includes simple enum arrays. */ private boolean isPoppingSimpleEnumType(int offset, int stackEntryIndex) { ReferenceValue referenceValue = partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue(); return isSimpleEnumType(referenceValue); } /** * Returns whether the given value is a simple enum type. This includes * simple enum arrays. */ private boolean isSimpleEnumType(ReferenceValue referenceValue) { return isSimpleEnum(referenceValue.getReferencedClass()); } /** * Returns whether the given class is not null and a simple enum class. */ private boolean isSimpleEnum(Clazz clazz) { return clazz != null && SimpleEnumMarker.isSimpleEnum(clazz); } /** * Marks the enum class of the popped type as complex. */ private void markConstantComplexEnumType(Clazz clazz, int constantIndex) { clazz.constantPoolEntryAccept(constantIndex, referencedComplexEnumMarker); } /** * Marks the enum class of the popped type as complex. */ private void markPoppedComplexEnumType(int offset) { markPoppedComplexEnumType(offset, 0); } /** * Marks the enum class of the specified popped type as complex. */ private void markPoppedComplexEnumType(int offset, int stackEntryIndex) { ReferenceValue referenceValue = partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue(); markComplexEnumType(referenceValue); } /** * Marks the enum class of the specified pushed type as complex. */ private void markPushedComplexEnumType(int offset) { ReferenceValue referenceValue = partialEvaluator.getStackAfter(offset).getTop(0).referenceValue(); markComplexEnumType(referenceValue); } /** * Marks the enum class of the specified stored type as complex. */ private void markStoredComplexEnumType(int offset, int variableIndex) { ReferenceValue referenceValue = partialEvaluator.getVariablesAfter(offset).getValue(variableIndex).referenceValue(); markComplexEnumType(referenceValue); } /** * Marks the enum class of the specified value as complex. */ private void markComplexEnumType(ReferenceValue referenceValue) { Clazz clazz = referenceValue.getReferencedClass(); if (clazz != null) { clazz.accept(complexEnumMarker); } } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/SimpleEnumUseSimplifier.java ================================================ /* * 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.evaluation; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.value.*; import proguard.optimize.info.SimpleEnumMarker; /** * This AttributeVisitor simplifies the use of enums in the code attributes that * it visits. * * @see SimpleEnumMarker * @see MemberReferenceFixer * @author Eric Lafortune */ public class SimpleEnumUseSimplifier implements AttributeVisitor, InstructionVisitor, ConstantVisitor, ParameterVisitor { private static final Logger logger = LogManager.getLogger(SimpleEnumUseSimplifier.class); private final InstructionVisitor extraInstructionVisitor; private final PartialEvaluator partialEvaluator; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true); private final ConstantVisitor nullParameterFixer = new ReferencedMemberVisitor(new AllParameterVisitor(false, this)); // Fields acting as parameters and return values for the visitor methods. private Clazz invocationClazz; private Method invocationMethod; private CodeAttribute invocationCodeAttribute; private int invocationOffset; private boolean isSimpleEnum; /** * Creates a new SimpleEnumUseSimplifier. */ public SimpleEnumUseSimplifier() { this(new PartialEvaluator(new TypedReferenceValueFactory()), null); } /** * Creates a new SimpleEnumDescriptorSimplifier. * @param partialEvaluator the partial evaluator that will * execute the code and provide * information about the results. * @param extraInstructionVisitor an optional extra visitor for all * simplified instructions. */ public SimpleEnumUseSimplifier(PartialEvaluator partialEvaluator, InstructionVisitor extraInstructionVisitor) { this.partialEvaluator = partialEvaluator; this.extraInstructionVisitor = extraInstructionVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { logger.debug("SimpleEnumUseSimplifier: {}.{}{}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz) ); // Skip the non-static methods of simple enum classes. if (SimpleEnumMarker.isSimpleEnum(clazz) && (method.getAccessFlags() & AccessConstants.STATIC) == 0) { return; } // Evaluate the method. partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); int codeLength = codeAttribute.u4codeLength; // Reset the code changes. codeAttributeEditor.reset(codeLength); // Replace any instructions that can be simplified. for (int offset = 0; offset < codeLength; offset++) { if (partialEvaluator.isTraced(offset)) { Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); instruction.accept(clazz, method, codeAttribute, offset, this); } } // Apply all accumulated changes to the code. codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } // Implementations for InstructionVisitor. public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { switch (simpleInstruction.opcode) { case Instruction.OP_AALOAD: { if (isPushingSimpleEnum(offset)) { // Load a simple enum integer from an integer array. replaceInstruction(clazz, offset, simpleInstruction, new SimpleInstruction(Instruction.OP_IALOAD)); } break; } case Instruction.OP_AASTORE: { if (isPoppingSimpleEnumArray(offset, 2)) { // Store a simple enum integer in an integer array. replaceInstruction(clazz, offset, simpleInstruction, new SimpleInstruction(Instruction.OP_IASTORE)); // Replace any producers of null constants. replaceNullStackEntryProducers(clazz, method, codeAttribute, offset); } break; } case Instruction.OP_ARETURN: { if (isReturningSimpleEnum(clazz, method)) { // Return a simple enum integer instead of an enum. replaceInstruction(clazz, offset, simpleInstruction, new SimpleInstruction(Instruction.OP_IRETURN)); // Replace any producers of null constants. replaceNullStackEntryProducers(clazz, method, codeAttribute, offset); } break; } } } public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { int variableIndex = variableInstruction.variableIndex; switch (variableInstruction.opcode) { case Instruction.OP_ALOAD: case Instruction.OP_ALOAD_0: case Instruction.OP_ALOAD_1: case Instruction.OP_ALOAD_2: case Instruction.OP_ALOAD_3: { if (isPushingSimpleEnum(offset)) { // Load a simple enum integer instead of an enum. replaceInstruction(clazz, offset, variableInstruction, new VariableInstruction(Instruction.OP_ILOAD, variableIndex)); // Replace any producers of null constants. replaceNullVariableProducers(clazz, method, codeAttribute, offset, variableIndex); } break; } case Instruction.OP_ASTORE: case Instruction.OP_ASTORE_0: case Instruction.OP_ASTORE_1: case Instruction.OP_ASTORE_2: case Instruction.OP_ASTORE_3: { if (!partialEvaluator.isSubroutineStart(offset) && isPoppingSimpleEnum(offset)) { // Store a simple enum integer instead of an enum. replaceInstruction(clazz, offset, variableInstruction, new VariableInstruction(Instruction.OP_ISTORE, variableIndex)); // Replace any producers of null constants. replaceNullStackEntryProducers(clazz, method, codeAttribute, offset); } break; } } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_PUTSTATIC: case Instruction.OP_PUTFIELD: { // Replace any producers of null constants. invocationClazz = clazz; invocationMethod = method; invocationCodeAttribute = codeAttribute; invocationOffset = offset; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, nullParameterFixer); break; } case Instruction.OP_INVOKEVIRTUAL: { // Check if the instruction is calling a simple enum. String invokedMethodName = clazz.getRefName(constantInstruction.constantIndex); String invokedMethodType = clazz.getRefType(constantInstruction.constantIndex); int stackEntryIndex = ClassUtil.internalMethodParameterSize(invokedMethodType); if (isPoppingSimpleEnum(offset, stackEntryIndex)) { replaceSupportedMethod(clazz, offset, constantInstruction, invokedMethodName, invokedMethodType); } // Fall through to check the parameters. } case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: case Instruction.OP_INVOKEINTERFACE: { // Replace any producers of null constants. invocationClazz = clazz; invocationMethod = method; invocationCodeAttribute = codeAttribute; invocationOffset = offset; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, nullParameterFixer); break; } case Instruction.OP_ANEWARRAY: { int constantIndex = constantInstruction.constantIndex; if (isReferencingSimpleEnum(clazz, constantIndex) && !ClassUtil.isInternalArrayType(clazz.getClassName(constantIndex))) { // Create an integer array instead of an enum array. replaceInstruction(clazz, offset, constantInstruction, new SimpleInstruction(Instruction.OP_NEWARRAY, Instruction.ARRAY_T_INT)); } break; } case Instruction.OP_CHECKCAST: { if (isPoppingSimpleEnum(offset)) { // Enum classes can only be simple if the checkcast // succeeds, so we can delete it. deleteInstruction(clazz, offset, constantInstruction); // Replace any producers of null constants. replaceNullStackEntryProducers(clazz, method, codeAttribute, offset); } break; } case Instruction.OP_INSTANCEOF: { if (isPoppingSimpleEnum(offset)) { // Enum classes can only be simple if the instanceof // succeeds, so we can push a constant result. replaceInstruction(clazz, offset, constantInstruction, new SimpleInstruction(Instruction.OP_ICONST_1)); // Replace any producers of null constants. replaceNullStackEntryProducers(clazz, method, codeAttribute, offset); } break; } } } public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { switch (branchInstruction.opcode) { case Instruction.OP_IFACMPEQ: { if (isPoppingSimpleEnum(offset)) { // Compare simple enum integers instead of enums. replaceInstruction(clazz, offset, branchInstruction, new BranchInstruction(Instruction.OP_IFICMPEQ, branchInstruction.branchOffset)); // Replace any producers of null constants. replaceNullStackEntryProducers(clazz, method, codeAttribute, offset, 0); replaceNullStackEntryProducers(clazz, method, codeAttribute, offset, 1); } break; } case Instruction.OP_IFACMPNE: { if (isPoppingSimpleEnum(offset)) { // Compare simple enum integers instead of enums. replaceInstruction(clazz, offset, branchInstruction, new BranchInstruction(Instruction.OP_IFICMPNE, branchInstruction.branchOffset)); // Replace any producers of null constants. replaceNullStackEntryProducers(clazz, method, codeAttribute, offset, 0); replaceNullStackEntryProducers(clazz, method, codeAttribute, offset, 1); } break; } case Instruction.OP_IFNULL: { if (isPoppingSimpleEnum(offset)) { // Compare with 0 instead of null. replaceInstruction(clazz, offset, branchInstruction, new BranchInstruction( Instruction.OP_IFEQ, branchInstruction.branchOffset)); } break; } case Instruction.OP_IFNONNULL: { if (isPoppingSimpleEnum(offset)) { // Compare with 0 instead of null. replaceInstruction(clazz, offset, branchInstruction, new BranchInstruction(Instruction.OP_IFNE, branchInstruction.branchOffset)); } break; } } } public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction) { } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Does the constant refer to a simple enum type? isSimpleEnum = isSimpleEnum(stringConstant.referencedClass); } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Does the constant refer to a simple enum type? isSimpleEnum = isSimpleEnum(classConstant.referencedClass); } // Implementations for ParameterVisitor. public void visitParameter(Clazz clazz, Member member, int parameterIndex, int parameterCount, int parameterOffset, int parameterSize, String parameterType, Clazz referencedClass) { // Check if the parameter is passing a simple enum as a more general type. if (!ClassUtil.isInternalPrimitiveType(parameterType.charAt(0)) && !ClassUtil.isInternalArrayType(parameterType) && isSimpleEnum(referencedClass)) { // Replace any producers of null constants for this parameter. int stackEntryIndex = parameterSize - parameterOffset - 1; replaceNullStackEntryProducers(invocationClazz, invocationMethod, invocationCodeAttribute, invocationOffset, stackEntryIndex); } } // Small utility methods. /** * Returns whether the constant at the given offset is referencing a * simple enum class. */ private boolean isReferencingSimpleEnum(Clazz clazz, int constantIndex) { isSimpleEnum = false; clazz.constantPoolEntryAccept(constantIndex, this); return isSimpleEnum; } /** * Returns whether the given method is returning a simple enum class. */ private boolean isReturningSimpleEnum(Clazz clazz, Method method) { String descriptor = method.getDescriptor(clazz); String returnType = ClassUtil.internalMethodReturnType(descriptor); if (ClassUtil.isInternalClassType(returnType) && !ClassUtil.isInternalArrayType(returnType)) { Clazz[] referencedClasses = ((ProgramMethod)method).referencedClasses; if (referencedClasses != null) { Clazz returnedClass = referencedClasses[referencedClasses.length - 1]; return isSimpleEnum(returnedClass); } } return false; } /** * Returns whether the instruction at the given offset is pushing a simple * enum class. */ private boolean isPushingSimpleEnum(int offset) { ReferenceValue referenceValue = partialEvaluator.getStackAfter(offset).getTop(0).referenceValue(); Clazz referencedClass = referenceValue.getReferencedClass(); return isSimpleEnum(referencedClass) && !ClassUtil.isInternalArrayType(referenceValue.getType()); } /** * Returns whether the instruction at the given offset is popping a simple * enum class. */ private boolean isPoppingSimpleEnum(int offset) { return isPoppingSimpleEnum(offset, 0); } /** * Returns whether the instruction at the given offset is popping a simple * enum class. */ private boolean isPoppingSimpleEnum(int offset, int stackEntryIndex) { ReferenceValue referenceValue = partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue(); return isSimpleEnum(referenceValue.getReferencedClass()) && !ClassUtil.isInternalArrayType(referenceValue.getType()); } /** * Returns whether the instruction at the given offset is popping a simple * enum type. This includes simple enum arrays. */ private boolean isPoppingSimpleEnumType(int offset, int stackEntryIndex) { ReferenceValue referenceValue = partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue(); return isSimpleEnum(referenceValue.getReferencedClass()); } /** * Returns whether the instruction at the given offset is popping a * one-dimensional simple enum array. */ private boolean isPoppingSimpleEnumArray(int offset, int stackEntryIndex) { ReferenceValue referenceValue = partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue(); return isSimpleEnum(referenceValue.getReferencedClass()) && ClassUtil.internalArrayTypeDimensionCount(referenceValue.getType()) == 1; } /** * Returns whether the given class is not null and a simple enum class. */ private boolean isSimpleEnum(Clazz clazz) { return clazz != null && SimpleEnumMarker.isSimpleEnum(clazz); } /** * Returns whether the specified enum method is supported for simple enums. */ private void replaceSupportedMethod(Clazz clazz, int offset, Instruction instruction, String name, String type) { if (name.equals(ClassConstants.METHOD_NAME_ORDINAL) && type.equals(ClassConstants.METHOD_TYPE_ORDINAL)) { Instruction[] replacementInstructions = new Instruction[] { new SimpleInstruction(Instruction.OP_ICONST_1), new SimpleInstruction(Instruction.OP_ISUB), }; replaceInstructions(clazz, offset, instruction, replacementInstructions); } } /** * Replaces the instruction at the given offset by the given instructions. */ private void replaceInstructions(Clazz clazz, int offset, Instruction instruction, Instruction[] replacementInstructions) { logger.debug(" Replacing instruction {} -> {} instructions", instruction.toString(offset), replacementInstructions.length ); codeAttributeEditor.replaceInstruction(offset, replacementInstructions); // Visit the instruction, if required. if (extraInstructionVisitor != null) { // Note: we're not passing the right arguments for now, knowing that // they aren't used anyway. instruction.accept(clazz, null, null, offset, extraInstructionVisitor); } } /** * Replaces the instruction at the given offset by the given instruction, * popping any now unused stack entries. */ private void replaceInstruction(Clazz clazz, int offset, Instruction instruction, Instruction replacementInstruction) { // Pop unneeded stack entries if necessary. int popCount = instruction.stackPopCount(clazz) - replacementInstruction.stackPopCount(clazz); insertPopInstructions(offset, popCount); logger.debug(" Replacing instruction {} -> {}{}", instruction.toString(offset), replacementInstruction.toString(), popCount == 0 ? "" : " ("+popCount+" pops)" ); codeAttributeEditor.replaceInstruction(offset, replacementInstruction); // Visit the instruction, if required. if (extraInstructionVisitor != null) { // Note: we're not passing the right arguments for now, knowing that // they aren't used anyway. instruction.accept(clazz, null, null, offset, extraInstructionVisitor); } } /** * Deletes the instruction at the given offset, popping any now unused * stack entries. */ private void deleteInstruction(Clazz clazz, int offset, Instruction instruction) { // Pop unneeded stack entries if necessary. //int popCount = instruction.stackPopCount(clazz); // //insertPopInstructions(offset, popCount); // //logger.debug(" Deleting instruction {}{}", instruction.toString(offset), popCount == 0 ? "" : " ("+popCount+" pops)"); logger.debug(" Deleting instruction {}", instruction.toString(offset)); codeAttributeEditor.deleteInstruction(offset); // Visit the instruction, if required. if (extraInstructionVisitor != null) { // Note: we're not passing the right arguments for now, knowing that // they aren't used anyway. instruction.accept(clazz, null, null, offset, extraInstructionVisitor); } } /** * Pops the given number of stack entries before the instruction at the * given offset. */ private void insertPopInstructions(int offset, int popCount) { switch (popCount) { case 0: { break; } case 1: { // Insert a single pop instruction. Instruction popInstruction = new SimpleInstruction(Instruction.OP_POP); codeAttributeEditor.insertBeforeInstruction(offset, popInstruction); break; } case 2: { // Insert a single pop2 instruction. Instruction popInstruction = new SimpleInstruction(Instruction.OP_POP2); codeAttributeEditor.insertBeforeInstruction(offset, popInstruction); break; } default: { // Insert the specified number of pop instructions. Instruction[] popInstructions = new Instruction[popCount / 2 + popCount % 2]; Instruction popInstruction = new SimpleInstruction(Instruction.OP_POP2); for (int index = 0; index < popCount / 2; index++) { popInstructions[index] = popInstruction; } if (popCount % 2 == 1) { popInstruction = new SimpleInstruction(Instruction.OP_POP); popInstructions[popCount / 2] = popInstruction; } codeAttributeEditor.insertBeforeInstruction(offset, popInstructions); break; } } } /** * Replaces aconst_null producers of the consumer of the top stack entry * at the given offset by iconst_0. */ private void replaceNullStackEntryProducers(Clazz clazz, Method method, CodeAttribute codeAttribute, int consumerOffset) { replaceNullStackEntryProducers(clazz, method, codeAttribute, consumerOffset, 0); } /** * Turn null reference producers of the specified stack entry into 0 int * producers. The partial evaluator generally can't identify them as * simple enums. */ private void replaceNullStackEntryProducers(Clazz clazz, Method method, CodeAttribute codeAttribute, int consumerOffset, int stackEntryIndex) { InstructionOffsetValue producerOffsets = partialEvaluator.getStackBefore(consumerOffset).getTopActualProducerValue(stackEntryIndex).instructionOffsetValue(); for (int index = 0; index < producerOffsets.instructionOffsetCount(); index++) { // Is the producer always pushing null? int producerOffset = producerOffsets.instructionOffset(index); if (producerOffset >= 0 && partialEvaluator.getStackAfter(producerOffset).getTop(0).referenceValue().isNull() == Value.ALWAYS) { Instruction producerInstruction = InstructionFactory.create(codeAttribute.code[producerOffset]); // Is it a simple case? switch (producerInstruction.opcode) { case Instruction.OP_ACONST_NULL: case Instruction.OP_ALOAD: case Instruction.OP_ALOAD_0: case Instruction.OP_ALOAD_1: case Instruction.OP_ALOAD_2: case Instruction.OP_ALOAD_3: { // Replace pushing null by pushing 0. replaceInstruction(clazz, producerOffset, producerInstruction, new SimpleInstruction(Instruction.OP_ICONST_0)); break; } default: { // Otherwise pop the null and then push 0. replaceInstructions(clazz, producerOffset, producerInstruction, new Instruction[] { producerInstruction, new SimpleInstruction(Instruction.OP_POP), new SimpleInstruction(Instruction.OP_ICONST_0) }); break; } } } } } /** * Turn null reference producers of the specified reference variable into * 0 int producers. The partial evaluator generally can't identify them * as simple enums. */ private void replaceNullVariableProducers(Clazz clazz, Method method, CodeAttribute codeAttribute, int consumerOffset, int variableIndex) { InstructionOffsetValue producerOffsets = partialEvaluator.getVariablesBefore(consumerOffset).getProducerValue(variableIndex).instructionOffsetValue(); for (int index = 0; index < producerOffsets.instructionOffsetCount(); index++) { if (!producerOffsets.isMethodParameter(index) && !producerOffsets.isExceptionHandler(index)) { // Is the producer always storing null? int producerOffset = producerOffsets.instructionOffset(index); if (partialEvaluator.getVariablesAfter(producerOffset).getValue(variableIndex).referenceValue().isNull() == Value.ALWAYS) { // Replace storing the null reference value by storing an // int value. replaceInstruction(clazz, producerOffset, new VariableInstruction(Instruction.OP_ASTORE, variableIndex), new VariableInstruction(Instruction.OP_ISTORE, variableIndex)); // Replace pushing null by pushing 0. replaceNullStackEntryProducers(clazz, method, codeAttribute, producerOffset); } } } } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/StoringInvocationUnit.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.evaluation; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.evaluation.BasicInvocationUnit; import proguard.evaluation.value.*; import proguard.optimize.KeepMarker; import proguard.optimize.info.*; /** * This InvocationUnit stores parameter values and return values with the * methods that are invoked. * * @see LoadingInvocationUnit * @author Eric Lafortune */ public class StoringInvocationUnit extends BasicInvocationUnit { private boolean storeFieldValues; private boolean storeMethodParameterValues; private boolean storeMethodReturnValues; /** * Creates a new StoringInvocationUnit with the given value factory. */ public StoringInvocationUnit(ValueFactory valueFactory) { this(valueFactory, true, true, true); } /** * Creates a new StoringInvocationUnit with the given value factory, for * storing the specified values. */ public StoringInvocationUnit(ValueFactory valueFactory, boolean storeFieldValues, boolean storeMethodParameterValues, boolean storeMethodReturnValues) { super(valueFactory); this.storeFieldValues = storeFieldValues; this.storeMethodParameterValues = storeMethodParameterValues; this.storeMethodReturnValues = storeMethodReturnValues; } // Implementations for BasicInvocationUnit. @Override public void setFieldClassValue(Clazz clazz, FieldrefConstant refConstant, ReferenceValue value) { if (storeFieldValues) { Field referencedMember = refConstant.referencedField; if (referencedMember != null) { generalizeFieldClassValue(referencedMember, value); } } } @Override public void setFieldValue(Clazz clazz, FieldrefConstant refConstant, Value value) { if (storeFieldValues) { Field referencedMember = refConstant.referencedField; if (referencedMember != null) { generalizeFieldValue(referencedMember, value); } } } @Override public void setMethodParameterValue(Clazz clazz, AnyMethodrefConstant refConstant, int parameterIndex, Value value) { if (storeMethodParameterValues) { Method referencedMember = refConstant.referencedMethod; if (referencedMember != null) { generalizeMethodParameterValue(referencedMember, parameterIndex, value); } } } @Override public void setMethodReturnValue(Clazz clazz, Method method, Value value) { if (storeMethodReturnValues) { generalizeMethodReturnValue(method, value); } } // Small utility methods. private static void generalizeFieldClassValue(Field field, ReferenceValue value) { if (!KeepMarker.isKept(field)) { ProgramFieldOptimizationInfo.getProgramFieldOptimizationInfo(field).generalizeReferencedClass(value); } } public static ReferenceValue getFieldClassValue(Field field) { return FieldOptimizationInfo.getFieldOptimizationInfo(field).getReferencedClass(); } private static void generalizeFieldValue(Field field, Value value) { if (!KeepMarker.isKept(field)) { ProgramFieldOptimizationInfo.getProgramFieldOptimizationInfo(field).generalizeValue(value); } } public static Value getFieldValue(Field field) { return FieldOptimizationInfo.getFieldOptimizationInfo(field).getValue(); } private static void generalizeMethodParameterValue(Method method, int parameterIndex, Value value) { if (!KeepMarker.isKept(method)) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).generalizeParameterValue(parameterIndex, value); } } public static Value getMethodParameterValue(Method method, int parameterIndex) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).getParameterValue(parameterIndex); } private static void generalizeMethodReturnValue(Method method, Value value) { if (!KeepMarker.isKept(method)) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).generalizeReturnValue(value); } } public static Value getMethodReturnValue(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).getReturnValue(); } } ================================================ FILE: base/src/main/java/proguard/optimize/evaluation/VariableOptimizer.java ================================================ /* * 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.evaluation; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.editor.*; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.LivenessAnalyzer; /** * This AttributeVisitor optimizes variable allocation based on their the liveness, * in the code attributes that it visits. * * @author Eric Lafortune */ public class VariableOptimizer implements AttributeVisitor, LocalVariableInfoVisitor, LocalVariableTypeInfoVisitor { private static final Logger logger = LogManager.getLogger(VariableOptimizer.class); private static final int MAX_VARIABLES_SIZE = 64; private final boolean reuseThis; private final MemberVisitor extraVariableMemberVisitor; private final LivenessAnalyzer livenessAnalyzer = new LivenessAnalyzer(); private final VariableRemapper variableRemapper = new VariableRemapper(); private VariableCleaner variableCleaner = new VariableCleaner(); private int[] variableMap = new int[ClassEstimates.TYPICAL_VARIABLES_SIZE]; /** * Creates a new VariableOptimizer. * @param reuseThis specifies whether the 'this' variable can be reused. * Many JVMs for JME and IBM's JVMs for JSE can't handle * such reuse. */ public VariableOptimizer(boolean reuseThis) { this(reuseThis, null); } /** * Creates a new VariableOptimizer with an extra visitor. * @param reuseThis specifies whether the 'this' variable * can be reused. Many JVMs for JME and * IBM's JVMs for JSE can't handle such * reuse. * @param extraVariableMemberVisitor an optional extra visitor for all * removed variables. */ public VariableOptimizer(boolean reuseThis, MemberVisitor extraVariableMemberVisitor) { this.reuseThis = reuseThis; this.extraVariableMemberVisitor = extraVariableMemberVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // DEBUG = // clazz.getName().equals("abc/Def") && // method.getName(clazz).equals("abc"); // Initialize the global arrays. initializeArrays(codeAttribute); // Analyze the liveness of the variables in the code. livenessAnalyzer.visitCodeAttribute(clazz, method, codeAttribute); // Trim the variables in the local variable tables, because even // clipping the tables individually may leave some inconsistencies // between them. codeAttribute.attributesAccept(clazz, method, this); int startIndex = (method.getAccessFlags() & AccessConstants.STATIC) != 0 || reuseThis ? 0 : 1; int parameterSize = ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz), method.getAccessFlags()); int variableSize = codeAttribute.u2maxLocals; int codeLength = codeAttribute.u4codeLength; boolean remapping = false; // Loop over all variables. for (int oldIndex = 0; oldIndex < variableSize; oldIndex++) { // By default, the variable will be mapped onto itself. variableMap[oldIndex] = oldIndex; // Only try remapping the variable if it's not a parameter. if (oldIndex >= parameterSize && oldIndex < MAX_VARIABLES_SIZE) { // Try to remap the variable to a variable with a smaller index. for (int newIndex = startIndex; newIndex < oldIndex; newIndex++) { if (areNonOverlapping(oldIndex, newIndex, codeLength)) { variableMap[oldIndex] = newIndex; updateLiveness(oldIndex, newIndex, codeLength); remapping = true; // This variable has been remapped. Go to the next one. break; } } } } // Have we been able to remap any variables? if (remapping) { logger.debug("VariableOptimizer: {}.{}{}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz) ); for (int index= 0; index < variableSize; index++) { logger.debug(" v{} -> {}", index, variableMap[index]); } // Remap the variables. variableRemapper.setVariableMap(variableMap); variableRemapper.visitCodeAttribute(clazz, method, codeAttribute); // Visit the method, if required. if (extraVariableMemberVisitor != null) { method.accept(clazz, extraVariableMemberVisitor); } } else { // Just clean up any empty variables. variableCleaner.visitCodeAttribute(clazz, method, codeAttribute); } } public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { // Trim the variables in the local variable table. localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { // Trim the variables in the local variable type table. localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } // Implementations for LocalVariableInfoVisitor. public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) { // Trim the local variable to the instructions at which it is alive. int variable = localVariableInfo.u2index; int startPC = localVariableInfo.u2startPC; int endPC = startPC + localVariableInfo.u2length; startPC = firstLiveness(startPC, endPC, variable); endPC = lastLiveness(startPC, endPC, variable); // Leave the start address of unused variables unchanged. int length = endPC - startPC; if (length > 0) { localVariableInfo.u2startPC = startPC; } localVariableInfo.u2length = length; } // Implementations for LocalVariableTypeInfoVisitor. public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) { // Trim the local variable type to the instructions at which it is alive. int variable = localVariableTypeInfo.u2index; int startPC = localVariableTypeInfo.u2startPC; int endPC = startPC + localVariableTypeInfo.u2length; startPC = firstLiveness(startPC, endPC, variable); endPC = lastLiveness(startPC, endPC, variable); // Leave the start address of unused variables unchanged. int length = endPC - startPC; if (length > 0) { localVariableTypeInfo.u2startPC = startPC; } localVariableTypeInfo.u2length = length; } // Small utility methods. /** * Initializes the global arrays. */ private void initializeArrays(CodeAttribute codeAttribute) { int codeLength = codeAttribute.u4codeLength; // Create new arrays for storing information at each instruction offset. if (variableMap.length < codeLength) { variableMap = new int[codeLength]; } } /** * Returns whether the given variables are never alive at the same time. */ private boolean areNonOverlapping(int variableIndex1, int variableIndex2, int codeLength) { // Loop over all instructions. for (int offset = 0; offset < codeLength; offset++) { if ((livenessAnalyzer.isAliveBefore(offset, variableIndex1) && livenessAnalyzer.isAliveBefore(offset, variableIndex2)) || (livenessAnalyzer.isAliveAfter(offset, variableIndex1) && livenessAnalyzer.isAliveAfter(offset, variableIndex2)) || // For now, exclude Category 2 variables. livenessAnalyzer.isCategory2(offset, variableIndex1)) { return false; } } return true; } /** * Updates the liveness resulting from mapping the given old variable on * the given new variable. */ private void updateLiveness(int oldVariableIndex, int newVariableIndex, int codeLength) { // Loop over all instructions. for (int offset = 0; offset < codeLength; offset++) { // Update the liveness before the instruction. if (livenessAnalyzer.isAliveBefore(offset, oldVariableIndex)) { livenessAnalyzer.setAliveBefore(offset, oldVariableIndex, false); livenessAnalyzer.setAliveBefore(offset, newVariableIndex, true); } // Update the liveness after the instruction. if (livenessAnalyzer.isAliveAfter(offset, oldVariableIndex)) { livenessAnalyzer.setAliveAfter(offset, oldVariableIndex, false); livenessAnalyzer.setAliveAfter(offset, newVariableIndex, true); } } } /** * Returns the first instruction offset between the given offsets at which * the given variable goes alive. */ private int firstLiveness(int startOffset, int endOffset, int variableIndex) { for (int offset = startOffset; offset < endOffset; offset++) { if (livenessAnalyzer.isTraced(offset) && livenessAnalyzer.isAliveBefore(offset, variableIndex)) { return offset; } } return endOffset; } /** * Returns the last instruction offset between the given offsets before * which the given variable is still alive. */ private int lastLiveness(int startOffset, int endOffset, int variableIndex) { int previousOffset = endOffset; for (int offset = endOffset-1; offset >= startOffset; offset--) { if (livenessAnalyzer.isTraced(offset)) { if (livenessAnalyzer.isAliveBefore(offset, variableIndex)) { return previousOffset; } previousOffset = offset; } } return endOffset; } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/DuplicateJsonFieldNameChecker.java ================================================ /* * 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.visitor.*; import java.util.*; /** * This class visitor checks whether the visited class has duplicate field names * in its JSON representation. * * @author Lars Vandenbergh */ class DuplicateJsonFieldNameChecker implements ClassVisitor { public boolean hasDuplicateJsonFieldNames; // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { for (OptimizedJsonFieldCollector.Mode mode : OptimizedJsonFieldCollector.Mode.values()) { OptimizedJsonInfo optimizedJsonInfo = new OptimizedJsonInfo(); OptimizedJsonFieldCollector jsonFieldCollector = new OptimizedJsonFieldCollector(optimizedJsonInfo, mode); programClass.accept(new MultiClassVisitor( jsonFieldCollector, new AllFieldVisitor(jsonFieldCollector))); OptimizedJsonInfo.ClassJsonInfo classJsonInfo = optimizedJsonInfo.classJsonInfos.get(programClass.getName()); Collection jsonFieldNamesCollection = classJsonInfo.javaToJsonFieldNames.values(); Set uniqueFieldNames = new HashSet(); for (String[] jsonFieldNames : jsonFieldNamesCollection) { for (String jsonFieldName : jsonFieldNames) { if (uniqueFieldNames.contains(jsonFieldName)) { hasDuplicateJsonFieldNames = true; return; } else { uniqueFieldNames.add(jsonFieldName); } } } } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/FieldSignatureCollector.java ================================================ /* * 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.*; import proguard.classfile.attribute.visitor.AttributeVisitor; /** * This visitor collects the Signature attribute of a Field. */ class FieldSignatureCollector implements AttributeVisitor { private String fieldSignature; public String getFieldSignature() { return fieldSignature; } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitSignatureAttribute(Clazz clazz, Field field, SignatureAttribute signatureAttribute) { this.fieldSignature = signatureAttribute.getSignature(clazz); } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonAnnotationCleaner.java ================================================ /* * 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.annotation.visitor.*; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.visitor.*; /** * This class visitor removes Gson annotations that are not required anymore * after the Gson optimizations are applied. * * @author Rob Coekaerts * @author Lars Vandenbergh */ public class GsonAnnotationCleaner implements ClassVisitor { private final GsonRuntimeSettings gsonRuntimeSettings; /** * Creates a new GsonAnnotationCleaner. * * @param gsonRuntimeSettings keeps track of all GsonBuilder invocations. */ public GsonAnnotationCleaner(GsonRuntimeSettings gsonRuntimeSettings) { this.gsonRuntimeSettings = gsonRuntimeSettings; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { final Object mark = new Object(); // Mark annotations when we are sure that they are not required // anymore by GSON. if (!gsonRuntimeSettings.setFieldNamingPolicy && !gsonRuntimeSettings.setFieldNamingStrategy) { programClass.fieldsAccept( new AllAttributeVisitor( new AllAnnotationVisitor( new AnnotationTypeFilter(GsonClassConstants.ANNOTATION_TYPE_SERIALIZED_NAME, new ProcessingInfoSetter(mark))))); } programClass.fieldsAccept( new AllAttributeVisitor( new AllAnnotationVisitor( new AnnotationTypeFilter(GsonClassConstants.ANNOTATION_TYPE_EXPOSE, new ProcessingInfoSetter(mark))))); // Remove marked annotations. programClass.fieldsAccept( new AllAttributeVisitor( new MarkedAnnotationDeleter(mark))); // Unmark all annotations on fields. programClass.fieldsAccept( new AllAttributeVisitor( new AllAnnotationVisitor( new ProcessingInfoSetter(null)))); } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonBuilderInvocationFinder.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.Constant; import proguard.classfile.editor.InstructionSequenceBuilder; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.evaluation.*; import proguard.evaluation.value.*; /** * This instructor visitor searches for invocations to GsonBuilder and keeps * track of which parameters of the GsonBuilder are being utilized in the code * in a GsonRuntimeSettings instance. * * @author Joachim Vandersmissen * @author Lars Vandenbergh */ public class GsonBuilderInvocationFinder implements InstructionVisitor { private final InstructionSequenceMatcher setVersionMatcher; private final InstructionSequenceMatcher excludeFieldsWithModifiersMatcher; private final InstructionSequenceMatcher generateNonExecutableJsonMatcher; private final InstructionSequenceMatcher excludeFieldsWithoutExposeAnnotationMatcher; private final InstructionSequenceMatcher serializeNullsMatcher; private final InstructionSequenceMatcher disableInnerClassSerializationMatcher; private final InstructionSequenceMatcher setLongSerializationPolicyMatcher; private final InstructionSequenceMatcher setFieldNamingPolicyMatcher; private final InstructionSequenceMatcher setFieldNamingStrategyMatcher; private final InstructionSequenceMatcher setExclusionStrategiesMatcher; private final InstructionSequenceMatcher addSerializationExclusionStrategyMatcher; private final InstructionSequenceMatcher addDeserializationExclusionStrategyMatcher; private final InstructionSequenceMatcher registerTypeAdapterMatcher; private final InstructionSequenceMatcher registerTypeHierachyAdapterMatcher; private final InstructionSequenceMatcher registerTypeAdapterFactoryMatcher; private final InstructionSequenceMatcher serializeSpecialFloatingPointValuesMatcher; private final TypedReferenceValueFactory valueFactory = new TypedReferenceValueFactory(); private final PartialEvaluator partialEvaluator = new PartialEvaluator(valueFactory, new BasicInvocationUnit(new TypedReferenceValueFactory()), true); private final AttributeVisitor lazyPartialEvaluator = new AttributeNameFilter(Attribute.CODE, new SingleTimeAttributeVisitor( partialEvaluator)); private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final GsonRuntimeSettings gsonRuntimeSettings; private final ClassVisitor instanceCreatorClassVisitor; private final ClassVisitor typeAdapterClassVisitor; /** * Creates a new GsonBuilderInvocationFinder. * * @param programClassPool the program class pool used to look * up class references. * @param libraryClassPool the library class pool used to look * up class references. * @param instanceCreatorClassVisitor visitor to which domain classes for * which an InstanceCreator is * registered will be delegated. * @param typeAdapterClassVisitor visitor to which domain classes for * which a TypeAdapter is registered * will be delegated. */ public GsonBuilderInvocationFinder(ClassPool programClassPool, ClassPool libraryClassPool, GsonRuntimeSettings gsonRuntimeSettings, ClassVisitor instanceCreatorClassVisitor, ClassVisitor typeAdapterClassVisitor) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.gsonRuntimeSettings = gsonRuntimeSettings; this.instanceCreatorClassVisitor = instanceCreatorClassVisitor; this.typeAdapterClassVisitor = typeAdapterClassVisitor; InstructionSequenceBuilder builder = new InstructionSequenceBuilder(); Instruction[] setVersionInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_SET_VERSION, GsonClassConstants.METHOD_TYPE_SET_VERSION) .instructions(); Instruction[] excludeFieldsWithModifiersInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_EXCLUDE_FIELDS_WITH_MODIFIERS, GsonClassConstants.METHOD_TYPE_EXCLUDE_FIELDS_WITH_MODIFIERS) .instructions(); Instruction[] generateNonExecutableJsonInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_GENERATE_EXECUTABLE_JSON, GsonClassConstants.METHOD_TYPE_GENERATE_EXECUTABLE_JSON) .instructions(); Instruction[] excludeFieldsWithoutExposeAnnotationInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_EXCLUDE_FIELDS_WITHOUT_EXPOSE_ANNOTATION, GsonClassConstants.METHOD_TYPE_EXLCUDE_FIELDS_WITHOUT_EXPOSE_ANNOTATION) .instructions(); Instruction[] serializeNullsInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_SERIALIZE_NULLS, GsonClassConstants.METHOD_TYPE_SERIALIZE_NULLS) .instructions(); Instruction[] enableComplexMapKeySerializationInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_ENABLE_COMPLEX_MAP_KEY_SERIALIZATION, GsonClassConstants.METHOD_TYPE_ENABLE_COMPLEX_MAP_KEY_SERIALIZATION) .instructions(); Instruction[] disableInnerClassSerializationInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_DISABLE_INNER_CLASS_SERIALIZATION, GsonClassConstants.METHOD_TYPE_DISABLE_INNER_CLASS_SERIALIZATION) .instructions(); Instruction[] setLongSerializationPolicyInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_SET_LONG_SERIALIZATION_POLICY, GsonClassConstants.METHOD_TYPE_SET_LONG_SERIALIZATION_POLICY) .instructions(); Instruction[] setFieldNamingStrategyInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_SET_FIELD_NAMING_STRATEGY, GsonClassConstants.METHOD_TYPE_SET_FIELD_NAMING_STRATEGY) .instructions(); Instruction[] setFieldNamingPolicyInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_SET_FIELD_NAMING_POLICY, GsonClassConstants.METHOD_TYPE_SET_FIELD_NAMING_POLICY) .instructions(); Instruction[] setExclusionStrategiesInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_SET_EXCLUSION_STRATEGIES, GsonClassConstants.METHOD_TYPE_SET_EXCLUSION_STRATEGIES) .instructions(); Instruction[] addSerializationExclusionStrategyInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_ADD_SERIALIZATION_EXCLUSION_STRATEGY, GsonClassConstants.METHOD_TYPE_ADD_SERIALIZATION_EXCLUSION_STRATEGY) .instructions(); Instruction[] addDeserializationExclusionStrategyInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_ADD_DESERIALIZATION_EXCLUSION_STRATEGY, GsonClassConstants.METHOD_TYPE_ADD_DESERIALIZATION_EXCLUSION_STRATEGY) .instructions(); Instruction[] registerTypeAdapterInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_REGISTER_TYPE_ADAPTER, GsonClassConstants.METHOD_TYPE_REGISTER_TYPE_ADAPTER) .instructions(); Instruction[] registerTypeHierarchyAdapterInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_REGISTER_TYPE_HIERARCHY_ADAPTER, GsonClassConstants.METHOD_TYPE_REGISTER_TYPE_HIERARCHY_ADAPTER) .instructions(); Instruction[] registerTypeAdapterFactoryInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_REGISTER_TYPE_ADAPTER_FACTORY, GsonClassConstants.METHOD_TYPE_REGISTER_TYPE_ADAPTER_FACTORY) .instructions(); Instruction[] serializeSpecialFloatingPointValuesInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON_BUILDER, GsonClassConstants.METHOD_NAME_SERIALIZE_SPECIAL_FLOATING_POINT_VALUES, GsonClassConstants.METHOD_TYPE_SERIALIZE_SPECIAL_FLOATING_POINT_VALUES) .instructions(); Constant[] constants = builder.constants(); setVersionMatcher = new InstructionSequenceMatcher(constants, setVersionInstructions); excludeFieldsWithModifiersMatcher = new InstructionSequenceMatcher(constants, excludeFieldsWithModifiersInstructions); generateNonExecutableJsonMatcher = new InstructionSequenceMatcher(constants, generateNonExecutableJsonInstructions); excludeFieldsWithoutExposeAnnotationMatcher = new InstructionSequenceMatcher(constants, excludeFieldsWithoutExposeAnnotationInstructions); serializeNullsMatcher = new InstructionSequenceMatcher(constants, serializeNullsInstructions); disableInnerClassSerializationMatcher = new InstructionSequenceMatcher(constants, disableInnerClassSerializationInstructions); setLongSerializationPolicyMatcher = new InstructionSequenceMatcher(constants, setLongSerializationPolicyInstructions); setFieldNamingPolicyMatcher = new InstructionSequenceMatcher(constants, setFieldNamingPolicyInstructions); setFieldNamingStrategyMatcher = new InstructionSequenceMatcher(constants, setFieldNamingStrategyInstructions); setExclusionStrategiesMatcher = new InstructionSequenceMatcher(constants, setExclusionStrategiesInstructions); addSerializationExclusionStrategyMatcher = new InstructionSequenceMatcher(constants, addSerializationExclusionStrategyInstructions); addDeserializationExclusionStrategyMatcher = new InstructionSequenceMatcher(constants, addDeserializationExclusionStrategyInstructions); registerTypeAdapterMatcher = new InstructionSequenceMatcher(constants, registerTypeAdapterInstructions); registerTypeHierachyAdapterMatcher = new InstructionSequenceMatcher(constants, registerTypeHierarchyAdapterInstructions); registerTypeAdapterFactoryMatcher = new InstructionSequenceMatcher(constants, registerTypeAdapterFactoryInstructions); serializeSpecialFloatingPointValuesMatcher = new InstructionSequenceMatcher(constants, serializeSpecialFloatingPointValuesInstructions); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { if (!gsonRuntimeSettings.setVersion) { instruction.accept(clazz, method, codeAttribute, offset, setVersionMatcher); gsonRuntimeSettings.setVersion = setVersionMatcher.isMatching(); } if (!gsonRuntimeSettings.excludeFieldsWithModifiers) { instruction.accept(clazz, method, codeAttribute, offset, excludeFieldsWithModifiersMatcher); gsonRuntimeSettings.excludeFieldsWithModifiers = excludeFieldsWithModifiersMatcher.isMatching(); } if (!gsonRuntimeSettings.generateNonExecutableJson) { instruction.accept(clazz, method, codeAttribute, offset, generateNonExecutableJsonMatcher); gsonRuntimeSettings.generateNonExecutableJson = generateNonExecutableJsonMatcher.isMatching(); } if (!gsonRuntimeSettings.excludeFieldsWithoutExposeAnnotation) { instruction.accept(clazz, method, codeAttribute, offset, excludeFieldsWithoutExposeAnnotationMatcher); gsonRuntimeSettings.excludeFieldsWithoutExposeAnnotation = excludeFieldsWithoutExposeAnnotationMatcher.isMatching(); } if (!gsonRuntimeSettings.serializeNulls) { instruction.accept(clazz, method, codeAttribute, offset, serializeNullsMatcher); gsonRuntimeSettings.serializeNulls = serializeNullsMatcher.isMatching(); } if (!gsonRuntimeSettings.disableInnerClassSerialization) { instruction.accept(clazz, method, codeAttribute, offset, disableInnerClassSerializationMatcher); gsonRuntimeSettings.disableInnerClassSerialization = disableInnerClassSerializationMatcher.isMatching(); } if (!gsonRuntimeSettings.setLongSerializationPolicy) { instruction.accept(clazz, method, codeAttribute, offset, setLongSerializationPolicyMatcher); gsonRuntimeSettings.setLongSerializationPolicy = setLongSerializationPolicyMatcher.isMatching(); } if (!gsonRuntimeSettings.setFieldNamingPolicy) { instruction.accept(clazz, method, codeAttribute, offset, setFieldNamingPolicyMatcher); gsonRuntimeSettings.setFieldNamingPolicy = setFieldNamingPolicyMatcher.isMatching(); } if (!gsonRuntimeSettings.setFieldNamingStrategy) { instruction.accept(clazz, method, codeAttribute, offset, setFieldNamingStrategyMatcher); gsonRuntimeSettings.setFieldNamingStrategy = setFieldNamingStrategyMatcher.isMatching(); } if (!gsonRuntimeSettings.setExclusionStrategies) { instruction.accept(clazz, method, codeAttribute, offset, setExclusionStrategiesMatcher); gsonRuntimeSettings.setExclusionStrategies = setExclusionStrategiesMatcher.isMatching(); } if (!gsonRuntimeSettings.addSerializationExclusionStrategy) { instruction.accept(clazz, method, codeAttribute, offset, addSerializationExclusionStrategyMatcher); gsonRuntimeSettings.addSerializationExclusionStrategy = addSerializationExclusionStrategyMatcher.isMatching(); } if (!gsonRuntimeSettings.addDeserializationExclusionStrategy) { instruction.accept(clazz, method, codeAttribute, offset, addDeserializationExclusionStrategyMatcher); gsonRuntimeSettings.addDeserializationExclusionStrategy = addDeserializationExclusionStrategyMatcher.isMatching(); } if (!gsonRuntimeSettings.serializeSpecialFloatingPointValues) { instruction.accept(clazz, method, codeAttribute, offset, serializeSpecialFloatingPointValuesMatcher); gsonRuntimeSettings.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValuesMatcher.isMatching(); } if (!gsonRuntimeSettings.registerTypeAdapterFactory) { instruction.accept(clazz, method, codeAttribute, offset, registerTypeAdapterFactoryMatcher); gsonRuntimeSettings.registerTypeAdapterFactory = registerTypeAdapterFactoryMatcher.isMatching(); } if (instanceCreatorClassVisitor != null && typeAdapterClassVisitor != null) { instruction.accept(clazz, method, codeAttribute, offset, registerTypeAdapterMatcher); instruction.accept(clazz, method, codeAttribute, offset, registerTypeHierachyAdapterMatcher); if (registerTypeAdapterMatcher.isMatching() || registerTypeHierachyAdapterMatcher.isMatching()) { // Figure out the class for which a type adapter is registered. lazyPartialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); // Derive Class from type argument. InstructionOffsetValue typeProducer = partialEvaluator.getStackBefore(offset) .getTopActualProducerValue(1) .instructionOffsetValue(); TypeArgumentFinder typeArgumentFinder = new TypeArgumentFinder(programClassPool, libraryClassPool, partialEvaluator); for (int i = 0; i < typeProducer.instructionOffsetCount(); i++) { codeAttribute.instructionAccept(clazz, method, typeProducer.instructionOffset(i), typeArgumentFinder); } if (typeArgumentFinder.typeArgumentClasses != null && typeArgumentFinder.typeArgumentClasses.length == 1) { String typeArgumentClass = typeArgumentFinder.typeArgumentClasses[0]; Clazz type = programClassPool.getClass(typeArgumentClass); if (type == null) { type = libraryClassPool.getClass(typeArgumentClass); } if (type != null) { // Derive Class from typeAdapter argument. InstructionOffsetValue typeAdapterProducer = partialEvaluator.getStackBefore(offset) .getTopActualProducerValue(0) .instructionOffsetValue(); TypeArgumentFinder typeAdapterArgumentFinder = new TypeArgumentFinder(programClassPool, libraryClassPool, partialEvaluator); for (int i = 0; i < typeAdapterProducer.instructionOffsetCount(); i++) { codeAttribute.instructionAccept(clazz, method, typeAdapterProducer.instructionOffset(i), typeAdapterArgumentFinder); } if (typeAdapterArgumentFinder.typeArgumentClasses != null && typeAdapterArgumentFinder.typeArgumentClasses.length == 1) { // Check whether type adapter passed as argument // implements InstanceCreator before passing the // domain type itself to the instanceCreatorClassVisitor. String typeAdapterArgumentClass = typeAdapterArgumentFinder.typeArgumentClasses[0]; Clazz instanceCreator = programClassPool.getClass(GsonClassConstants.NAME_INSTANCE_CREATOR); ImplementedClassFilter implementsInstanceCreatorFilter = new ImplementedClassFilter(instanceCreator, false, new ClassVisitorPropagator(type, instanceCreatorClassVisitor), new ClassVisitorPropagator(type, typeAdapterClassVisitor)); programClassPool.classAccept(typeAdapterArgumentClass, implementsInstanceCreatorFilter); libraryClassPool.classAccept(typeAdapterArgumentClass, implementsInstanceCreatorFilter); } } } } } } private static class ClassVisitorPropagator implements ClassVisitor { private final Clazz clazz; private final ClassVisitor classVisitor; private ClassVisitorPropagator(Clazz clazz, ClassVisitor classVisitor) { this.clazz = clazz; this.classVisitor = classVisitor; } // Implementations for ClassVisitor @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { clazz.accept(classVisitor); } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonClassConstants.java ================================================ /* * 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; /** * Constants used in the classes of the GSON library. * * @author Lars Vandenbergh */ public class GsonClassConstants { public static final String NAME_GSON_BUILDER = "com/google/gson/GsonBuilder"; public static final String METHOD_NAME_ADD_DESERIALIZATION_EXCLUSION_STRATEGY = "addDeserializationExclusionStrategy"; public static final String METHOD_TYPE_ADD_DESERIALIZATION_EXCLUSION_STRATEGY = "(Lcom/google/gson/ExclusionStrategy;)Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_ADD_SERIALIZATION_EXCLUSION_STRATEGY = "addSerializationExclusionStrategy"; public static final String METHOD_TYPE_ADD_SERIALIZATION_EXCLUSION_STRATEGY = "(Lcom/google/gson/ExclusionStrategy;)Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_DISABLE_INNER_CLASS_SERIALIZATION = "disableInnerClassSerialization"; public static final String METHOD_TYPE_DISABLE_INNER_CLASS_SERIALIZATION = "()Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_ENABLE_COMPLEX_MAP_KEY_SERIALIZATION = "enableComplexMapKeySerialization"; public static final String METHOD_TYPE_ENABLE_COMPLEX_MAP_KEY_SERIALIZATION = "()Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_EXCLUDE_FIELDS_WITH_MODIFIERS = "excludeFieldsWithModifiers"; public static final String METHOD_TYPE_EXCLUDE_FIELDS_WITH_MODIFIERS = "([I)Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_EXCLUDE_FIELDS_WITHOUT_EXPOSE_ANNOTATION = "excludeFieldsWithoutExposeAnnotation"; public static final String METHOD_TYPE_EXLCUDE_FIELDS_WITHOUT_EXPOSE_ANNOTATION = "()Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_GENERATE_EXECUTABLE_JSON = "generateNonExecutableJson"; public static final String METHOD_TYPE_GENERATE_EXECUTABLE_JSON = "()Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_REGISTER_TYPE_ADAPTER = "registerTypeAdapter"; public static final String METHOD_TYPE_REGISTER_TYPE_ADAPTER = "(Ljava/lang/reflect/Type;Ljava/lang/Object;)Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_REGISTER_TYPE_HIERARCHY_ADAPTER = "registerTypeHierarchyAdapter"; public static final String METHOD_TYPE_REGISTER_TYPE_HIERARCHY_ADAPTER = "(Ljava/lang/Class;Ljava/lang/Object;)Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_REGISTER_TYPE_ADAPTER_FACTORY = "registerTypeAdapterFactory"; public static final String METHOD_TYPE_REGISTER_TYPE_ADAPTER_FACTORY = "(Lcom/google/gson/TypeAdapterFactory;)Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_SERIALIZE_NULLS = "serializeNulls"; public static final String METHOD_TYPE_SERIALIZE_NULLS = "()Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_SERIALIZE_SPECIAL_FLOATING_POINT_VALUES = "serializeSpecialFloatingPointValues"; public static final String METHOD_TYPE_SERIALIZE_SPECIAL_FLOATING_POINT_VALUES = "()Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_SET_EXCLUSION_STRATEGIES = "setExclusionStrategies"; public static final String METHOD_TYPE_SET_EXCLUSION_STRATEGIES = "([Lcom/google/gson/ExclusionStrategy;)Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_SET_FIELD_NAMING_POLICY = "setFieldNamingPolicy"; public static final String METHOD_TYPE_SET_FIELD_NAMING_POLICY = "(Lcom/google/gson/FieldNamingPolicy;)Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_SET_FIELD_NAMING_STRATEGY = "setFieldNamingStrategy"; public static final String METHOD_TYPE_SET_FIELD_NAMING_STRATEGY = "(Lcom/google/gson/FieldNamingStrategy;)Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_SET_LONG_SERIALIZATION_POLICY = "setLongSerializationPolicy"; public static final String METHOD_TYPE_SET_LONG_SERIALIZATION_POLICY = "(Lcom/google/gson/LongSerializationPolicy;)Lcom/google/gson/GsonBuilder;"; public static final String METHOD_NAME_SET_VERSION = "setVersion"; public static final String METHOD_TYPE_SET_VERSION = "(D)Lcom/google/gson/GsonBuilder;"; public static final String NAME_GSON = "com/google/gson/Gson"; public static final String FIELD_NAME_EXCLUDER = "excluder"; public static final String FIELD_TYPE_EXCLUDER = "Lcom/google/gson/internal/Excluder;"; public static final String FIELD_NAME_FIELD_NAMING_STRATEGY = "fieldNamingStrategy"; public static final String FIELD_TYPE_FIELD_NAMING_STRATEGY = "Lcom/google/gson/FieldNamingStrategy;"; public static final String FIELD_NAME_INSTANCE_CREATORS = "instanceCreators"; public static final String FIELD_TYPE_INSTANCE_CREATORS = "Ljava/util/Map;"; public static final String FIELD_NAME_TYPE_TOKEN_CACHE = "typeTokenCache"; public static final String FIELD_TYPE_TYPE_TOKEN_CACHE = "Ljava/util/Map;"; public static final String METHOD_NAME_GET_ADAPTER_CLASS = "getAdapter"; public static final String METHOD_TYPE_GET_ADAPTER_CLASS = "(Ljava/lang/Class;)Lcom/google/gson/TypeAdapter;"; public static final String METHOD_NAME_GET_ADAPTER_TYPE_TOKEN = "getAdapter"; public static final String METHOD_TYPE_GET_ADAPTER_TYPE_TOKEN = "(Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;"; public static final String METHOD_NAME_TO_JSON = "toJson"; public static final String METHOD_TYPE_TO_JSON_OBJECT = "(Ljava/lang/Object;)Ljava/lang/String;"; public static final String METHOD_TYPE_TO_JSON_OBJECT_TYPE = "(Ljava/lang/Object;Ljava/lang/reflect/Type;)Ljava/lang/String;"; public static final String METHOD_TYPE_TO_JSON_OBJECT_APPENDABLE = "(Ljava/lang/Object;Ljava/lang/Appendable;)V"; public static final String METHOD_TYPE_TO_JSON_OBJECT_TYPE_APPENDABLE = "(Ljava/lang/Object;Ljava/lang/reflect/Type;Ljava/lang/Appendable;)V"; public static final String METHOD_TYPE_TO_JSON_OBJECT_TYPE_WRITER = "(Ljava/lang/Object;Ljava/lang/reflect/Type;Lcom/google/gson/stream/JsonWriter;)V"; public static final String METHOD_TYPE_TO_JSON_JSON_ELEMENT_WRITER = "(Lcom/google/gson/JsonElement;Lcom/google/gson/stream/JsonWriter;)V"; public static final String METHOD_NAME_TO_JSON_TREE = "toJsonTree"; public static final String METHOD_TYPE_TO_JSON_TREE_OBJECT = "(Ljava/lang/Object;)Lcom/google/gson/JsonElement;"; public static final String METHOD_TYPE_TO_JSON_TREE_OBJECT_TYPE = "(Ljava/lang/Object;Ljava/lang/reflect/Type;)Lcom/google/gson/JsonElement;"; public static final String METHOD_NAME_FROM_JSON = "fromJson"; public static final String METHOD_TYPE_FROM_JSON_STRING_CLASS = "(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"; public static final String METHOD_TYPE_FROM_JSON_STRING_TYPE = "(Ljava/lang/String;Ljava/lang/reflect/Type;)Ljava/lang/Object;"; public static final String METHOD_TYPE_FROM_JSON_READER_CLASS = "(Ljava/io/Reader;Ljava/lang/Class;)Ljava/lang/Object;"; public static final String METHOD_TYPE_FROM_JSON_READER_TYPE = "(Ljava/io/Reader;Ljava/lang/reflect/Type;)Ljava/lang/Object;"; public static final String METHOD_TYPE_FROM_JSON_JSON_READER_TYPE = "(Lcom/google/gson/stream/JsonReader;Ljava/lang/reflect/Type;)Ljava/lang/Object;"; public static final String NAME_TYPE_TOKEN = "com/google/gson/reflect/TypeToken"; public static final String METHOD_NAME_GET_TYPE = "getType"; public static final String METHOD_TYPE_GET_TYPE = "()Ljava/lang/reflect/Type;"; public static final String METHOD_NAME_GET_RAW_TYPE = "getRawType"; public static final String METHOD_TYPE_GET_RAW_TYPE = "()Ljava/lang/Class;"; public static final String NAME_EXCLUDER = "com/google/gson/internal/Excluder"; public static final String FIELD_NAME_DESERIALIZATION_STRATEGIES = "deserializationStrategies"; public static final String FIELD_TYPE_DESERIALIZATION_STRATEGIES = "Ljava/util/List;"; public static final String FIELD_NAME_MODIFIERS = "modifiers"; public static final String FIELD_TYPE_MODIFIERS = "I"; public static final String FIELD_NAME_SERIALIZATION_STRATEGIES = "serializationStrategies"; public static final String FIELD_TYPE_SERIALIZATION_STRATEGIES = "Ljava/util/List;"; public static final String FIELD_NAME_VERSION = "version"; public static final String FIELD_TYPE_VERSION = "D"; public static final String NAME_INSTANCE_CREATOR = "com/google/gson/InstanceCreator"; public static final String METHOD_NAME_CREATE_INSTANCE = "createInstance"; public static final String METHOD_TYPE_CREATE_INSTANCE = "(Ljava/lang/reflect/Type;)Ljava/lang/Object;"; public static final String NAME_TYPE_ADAPTER = "com/google/gson/TypeAdapter"; public static final String METHOD_NAME_READ = "read"; public static final String METHOD_TYPE_READ = "(Lcom/google/gson/stream/JsonReader;)Ljava/lang/Object;"; public static final String METHOD_NAME_WRITE = "write"; public static final String METHOD_TYPE_WRITE = "(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Object;)V"; public static final String NAME_FIELD_NAMING_POLICY = "com/google/gson/FieldNamingPolicy"; public static final String FIELD_NAME_IDENTITY = "IDENTITY"; public static final String FIELD_TYPE_IDENTITY = "Lcom/google/gson/FieldNamingPolicy;"; public static final String NAME_JSON_READER = "com/google/gson/stream/JsonReader"; public static final String METHOD_NAME_READER_BEGIN_OBJECT = "beginObject"; public static final String METHOD_TYPE_READER_BEGIN_OBJECT = "()V"; public static final String METHOD_NAME_READER_END_OBJECT = "endObject"; public static final String METHOD_TYPE_READER_END_OBJECT = "()V"; public static final String METHOD_NAME_NEXT_STRING = "nextString"; public static final String METHOD_TYPE_NEXT_STRING = "()Ljava/lang/String;"; public static final String METHOD_NAME_NEXT_BOOLEAN = "nextBoolean"; public static final String METHOD_TYPE_NEXT_BOOLEAN = "()Z"; public static final String METHOD_NAME_NEXT_INTEGER = "nextInt"; public static final String METHOD_TYPE_NEXT_INTEGER = "()I"; public static final String METHOD_NAME_NEXT_NULL = "nextNull"; public static final String METHOD_TYPE_NEXT_NULL = "()V"; public static final String METHOD_NAME_SKIP_VALUE = "skipValue"; public static final String METHOD_TYPE_SKIP_VALUE = "()V"; public static final String NAME_JSON_WRITER = "com/google/gson/stream/JsonWriter"; public static final String METHOD_NAME_WRITER_BEGIN_OBJECT = "beginObject"; public static final String METHOD_TYPE_WRITER_BEGIN_OBJECT = "()Lcom/google/gson/stream/JsonWriter;"; public static final String METHOD_NAME_WRITER_END_OBJECT = "endObject"; public static final String METHOD_TYPE_WRITER_END_OBJECT = "()Lcom/google/gson/stream/JsonWriter;"; public static final String METHOD_NAME_HAS_NEXT = "hasNext"; public static final String METHOD_TYPE_HAS_NEXT = "()Z"; public static final String METHOD_NAME_PEEK = "peek"; public static final String METHOD_TYPE_PEEK = "()Lcom/google/gson/stream/JsonToken;"; public static final String METHOD_NAME_NULL_VALUE = "nullValue"; public static final String METHOD_TYPE_NULL_VALUE = "()Lcom/google/gson/stream/JsonWriter;"; public static final String METHOD_NAME_VALUE_BOOLEAN = "value"; public static final String METHOD_TYPE_VALUE_BOOLEAN = "(Z)Lcom/google/gson/stream/JsonWriter;"; public static final String METHOD_NAME_VALUE_BOOLEAN_OBJECT = "value"; public static final String METHOD_TYPE_VALUE_BOOLEAN_OBJECT = "(Ljava/lang/Boolean;)Lcom/google/gson/stream/JsonWriter;"; public static final String METHOD_NAME_VALUE_NUMBER = "value"; public static final String METHOD_TYPE_VALUE_NUMBER = "(Ljava/lang/Number;)Lcom/google/gson/stream/JsonWriter;"; public static final String METHOD_NAME_VALUE_STRING = "value"; public static final String METHOD_TYPE_NAME_VALUE_STRING = "(Ljava/lang/String;)Lcom/google/gson/stream/JsonWriter;"; public static final String NAME_JSON_SYNTAX_EXCEPTION = "com/google/gson/JsonSyntaxException"; public static final String NAME_JSON_TOKEN = "com/google/gson/stream/JsonToken"; public static final String FIELD_NAME_NULL = "NULL"; public static final String FIELD_TYPE_NULL = "Lcom/google/gson/stream/JsonToken;"; public static final String FIELD_NAME_BOOLEAN = "BOOLEAN"; public static final String FIELD_TYPE_BOOLEAN = "Lcom/google/gson/stream/JsonToken;"; public static final String ANNOTATION_TYPE_EXPOSE = "Lcom/google/gson/annotations/Expose;"; public static final String ANNOTATION_TYPE_JSON_ADAPTER = "Lcom/google/gson/annotations/JsonAdapter;"; public static final String ANNOTATION_TYPE_SERIALIZED_NAME = "Lcom/google/gson/annotations/SerializedName;"; } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonConstructorPatcher.java ================================================ /* * 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.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.*; import proguard.evaluation.value.*; /** * Class visitor that patches the constructor of Gson so that the injected * optimized type adapter factory is registered at the right priority. It * also exposes the Excluder used by Gson to the outside if needed. * * @author Lars Vandenbergh */ public class GsonConstructorPatcher implements MemberVisitor, AttributeVisitor, InstructionVisitor { private static final Logger logger = LogManager.getLogger(GsonConstructorPatcher.class); private final CodeAttributeEditor codeAttributeEditor; private final TypedReferenceValueFactory valueFactory = new TypedReferenceValueFactory(); private final PartialEvaluator partialEvaluator = new PartialEvaluator(valueFactory, new BasicInvocationUnit(new TypedReferenceValueFactory()), true); private final AttributeVisitor lazyPartialEvaluator = new AttributeNameFilter(Attribute.CODE, new SingleTimeAttributeVisitor( partialEvaluator)); private final static int THIS_PARAMETER = 0; private final static int EXCLUDER_PARAMETER = 1; private int insertionOffset = -1; private int typeAdapterListLocal = -1; private boolean addExcluder; /** * Constructs a new GsonConstructorPatcher. * * @param codeAttributeEditor the code attribute editor for editing the * code attribute of the Gson constructor. * @param addExcluder determines whether or not to inject * code for exposing the Gson excluder. */ public GsonConstructorPatcher(CodeAttributeEditor codeAttributeEditor, boolean addExcluder) { this.codeAttributeEditor = codeAttributeEditor; this.addExcluder = addExcluder; } // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) {} @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // We make the assumption that there is one constructor with a List // of type adapter factories as one of its arguments. This has been // the case since Gson version 2.0 from 2011. String descriptor = programMethod.getDescriptor(programClass); if (descriptor.contains(ClassConstants.TYPE_JAVA_UTIL_LIST)) { logger.debug("GsonConstructorPatcher: patching {} {} {}", programClass.getName(), programMethod.getName(programClass), descriptor ); programMethod.attributesAccept(programClass, this); } } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Search for insertion point and local that contains list of type // adapter factories. codeAttribute.instructionsAccept(clazz, method, this); if (insertionOffset != -1 && typeAdapterListLocal != -1) { // Set up the code attribute editor. codeAttributeEditor.reset(codeAttribute.u4codeLength); // Insert instructions for appending type adapter factory to the list. InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz); ____.new_(ClassConstants.NAME_JAVA_UTIL_ARRAY_LIST) .dup() .aload(typeAdapterListLocal) .invokespecial(ClassConstants.NAME_JAVA_UTIL_ARRAY_LIST, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT_COLLECTION) .astore(typeAdapterListLocal) .aload(typeAdapterListLocal) .new_(OptimizedClassConstants.NAME_OPTIMIZED_TYPE_ADAPTER_FACTORY) .dup() .invokespecial(OptimizedClassConstants.NAME_OPTIMIZED_TYPE_ADAPTER_FACTORY, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT) .invokeinterface(ClassConstants.NAME_JAVA_UTIL_LIST, ClassConstants.METHOD_NAME_ADD, ClassConstants.METHOD_TYPE_ADD) .pop(); // Insert instructions for assigning excluder to the artificial excluder field. if (addExcluder) { ____.aload(THIS_PARAMETER) .aload(EXCLUDER_PARAMETER) .putfield(GsonClassConstants.NAME_GSON, OptimizedClassConstants.FIELD_NAME_EXCLUDER, OptimizedClassConstants.FIELD_TYPE_EXCLUDER); } codeAttributeEditor.insertAfterInstruction(insertionOffset, ____.instructions()); // Apply the insertion. codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } } // Implementations for InstructionVisitor @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { if (instruction.actualOpcode() == Instruction.OP_INVOKEINTERFACE && typeAdapterListLocal == -1) { ConstantInstruction constantInstruction = (ConstantInstruction)instruction; Constant constant = ((ProgramClass)clazz).constantPool[constantInstruction.constantIndex]; if (constant instanceof InterfaceMethodrefConstant) { InterfaceMethodrefConstant interfaceMethodrefConstant = (InterfaceMethodrefConstant)constant; if (interfaceMethodrefConstant.getClassName(clazz).equals(ClassConstants.NAME_JAVA_UTIL_LIST) && interfaceMethodrefConstant.getName(clazz).equals(ClassConstants.METHOD_NAME_ADD_ALL) && interfaceMethodrefConstant.getType(clazz).equals(ClassConstants.METHOD_TYPE_ADD_ALL)) { // We found an invocation to List.add(Object). // Find out which instructions contributed to the top value // on the stack and visit them to determine which local is // passed as argument. lazyPartialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); TracedStack stackBefore = partialEvaluator.getStackBefore(offset); InstructionOffsetValue instructionOffsetValue = stackBefore.getTopProducerValue(0).instructionOffsetValue(); for (int instructionIndex = 0; instructionIndex < instructionOffsetValue.instructionOffsetCount(); instructionIndex++) { int instructionOffset = instructionOffsetValue.instructionOffset(instructionIndex); codeAttribute.instructionAccept(clazz, method, instructionOffset, new LocalFinder()); } } } } else if (instruction.actualOpcode() == Instruction.OP_INVOKESPECIAL && insertionOffset == -1) { ConstantInstruction constantInstruction = (ConstantInstruction)instruction; Constant constant = ((ProgramClass)clazz).constantPool[constantInstruction.constantIndex]; if (constant instanceof MethodrefConstant) { MethodrefConstant methodrefConstant = (MethodrefConstant)constant; if (methodrefConstant.getClassName(clazz).equals(ClassConstants.NAME_JAVA_LANG_OBJECT) && methodrefConstant.getName(clazz).equals(ClassConstants.METHOD_NAME_INIT) && methodrefConstant.getType(clazz).equals(ClassConstants.METHOD_TYPE_INIT)) { // We want to insert our patch after the call to Object.. insertionOffset = offset; } } } } private class LocalFinder implements InstructionVisitor { // Implementations for InstructionVisitor @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { if (instruction.canonicalOpcode() == Instruction.OP_ALOAD) { VariableInstruction variableInstruction = (VariableInstruction)instruction; typeAdapterListLocal = variableInstruction.variableIndex; } } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonContext.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.AllAttributeVisitor; import proguard.classfile.instruction.visitor.*; import proguard.classfile.util.WarningPrinter; import proguard.classfile.visitor.*; /** * This class groups all information about how the Gson library is being used * in a program class pool. * * @author Lars Vandenbergh */ public class GsonContext { public ClassPool gsonDomainClassPool; public GsonRuntimeSettings gsonRuntimeSettings; /** * Sets up the Gson context for the given program class pool. * Notes will be printed to the given printer if provided. * * @param programClassPool the program class pool * @param libraryClassPool the library class pool * @param warningPrinter the optional warning printer to which notes * can be printed. */ public void setupFor(ClassPool programClassPool, ClassPool libraryClassPool, WarningPrinter warningPrinter) { // Only apply remaining optimizations to classes that are not part of // Gson itself. ClassPool filteredClasses = new ClassPool(); programClassPool.classesAccept( new ClassNameFilter("!com/google/gson/**", new ClassPoolFiller(filteredClasses))); // Find all GsonBuilder invocations. gsonRuntimeSettings = new GsonRuntimeSettings(); GsonBuilderInvocationFinder gsonBuilderInvocationFinder = new GsonBuilderInvocationFinder( programClassPool, libraryClassPool, gsonRuntimeSettings, new ClassPoolFiller(gsonRuntimeSettings.instanceCreatorClassPool), new ClassPoolFiller(gsonRuntimeSettings.typeAdapterClassPool)); filteredClasses.classesAccept( new AllMethodVisitor( new AllAttributeVisitor( new AllInstructionVisitor(gsonBuilderInvocationFinder)))); // Find all Gson invocations. gsonDomainClassPool = new ClassPool(); GsonDomainClassFinder domainClassFinder = new GsonDomainClassFinder(gsonRuntimeSettings, gsonDomainClassPool, warningPrinter); filteredClasses.accept( new AllClassVisitor( new AllMethodVisitor( new AllAttributeVisitor( new AllInstructionVisitor( new MultiInstructionVisitor( new GsonSerializationInvocationFinder(programClassPool, libraryClassPool, domainClassFinder, warningPrinter), new GsonDeserializationInvocationFinder(programClassPool, libraryClassPool, domainClassFinder, warningPrinter))))))); } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonDeserializationInvocationFinder.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.Constant; import proguard.classfile.editor.InstructionSequenceBuilder; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.ClassVisitor; import proguard.evaluation.*; import proguard.evaluation.value.*; /** * This instruction visitor searches the code for invocations to any of the * deserialization methods of Gson (all the fromJson variants) and keeps * track of the domain classes that are involved in the deserialization. * * @author Lars Vandenbergh */ public class GsonDeserializationInvocationFinder implements InstructionVisitor { private static final Logger logger = LogManager.getLogger(GsonDeserializationInvocationFinder.class); private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final ClassVisitor domainClassVisitor; private final WarningPrinter warningPrinter; private final FromJsonInvocationMatcher[] fromJsonInvocationMatchers; private final TypedReferenceValueFactory valueFactory = new TypedReferenceValueFactory(); private final PartialEvaluator partialEvaluator = new PartialEvaluator(valueFactory, new BasicInvocationUnit(new TypedReferenceValueFactory()), true); private final AttributeVisitor lazyPartialEvaluator = new AttributeNameFilter(Attribute.CODE, new SingleTimeAttributeVisitor( partialEvaluator)); /** * Creates a new GsonDeserializationInvocationFinder. * * @param programClassPool the program class pool used to look up class * references. * @param libraryClassPool the library class pool used to look up class * references. * @param domainClassVisitor the visitor to which found domain classes that * are involved in Gson deserialization will * be delegated. * @param warningPrinter used to print notes about domain classes that * can not be handled by the Gson optimization. */ public GsonDeserializationInvocationFinder(ClassPool programClassPool, ClassPool libraryClassPool, ClassVisitor domainClassVisitor, WarningPrinter warningPrinter) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.domainClassVisitor = domainClassVisitor; this.warningPrinter = warningPrinter; // Create matchers for relevant instruction sequences. InstructionSequenceBuilder builder = new InstructionSequenceBuilder(); // The invocation "Gson#fromJson(String, Class)". Instruction[] fromJsonStringClassInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_FROM_JSON, GsonClassConstants.METHOD_TYPE_FROM_JSON_STRING_CLASS) .instructions(); // The invocation "Gson#fromJson(String, Type)". Instruction[] fromJsonStringTypeInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_FROM_JSON, GsonClassConstants.METHOD_TYPE_FROM_JSON_STRING_TYPE) .instructions(); // The invocation "Gson#fromJson(Reader, Class)". Instruction[] fromJsonReaderClassInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_FROM_JSON, GsonClassConstants.METHOD_TYPE_FROM_JSON_READER_CLASS) .instructions(); // The invocation "Gson#fromJson(Reader, Type)". Instruction[] fromJsonReaderTypeInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_FROM_JSON, GsonClassConstants.METHOD_TYPE_FROM_JSON_READER_TYPE) .instructions(); // The invocation "Gson#fromJson(JsonReader, Type)". Instruction[] fromJsonJsonReaderTypeInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_FROM_JSON, GsonClassConstants.METHOD_TYPE_FROM_JSON_JSON_READER_TYPE) .instructions(); Constant[] constants = builder.constants(); fromJsonInvocationMatchers = new FromJsonInvocationMatcher[] { new FromJsonInvocationMatcher(constants, fromJsonStringClassInstructions, 0, -1), new FromJsonInvocationMatcher(constants, fromJsonStringTypeInstructions, -1, 0), new FromJsonInvocationMatcher(constants, fromJsonReaderClassInstructions, 0, -1), new FromJsonInvocationMatcher(constants, fromJsonReaderTypeInstructions, -1, 0), new FromJsonInvocationMatcher(constants, fromJsonJsonReaderTypeInstructions, -1, 0) }; } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // Try to match any of the fromJson() constructs. FromJsonInvocationMatcher matchingMatcher = null; for (FromJsonInvocationMatcher matcher : fromJsonInvocationMatchers) { instruction.accept(clazz, method, codeAttribute, offset, matcher); if(matcher.isMatching()) { matchingMatcher = matcher; break; } } if (matchingMatcher != null) { logger.debug("GsonDeserializationInvocationFinder: Gson#fromJson: {}.{}{} {}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), instruction.toString(offset) ); // Figure out the type that is being deserialized. lazyPartialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); // Derive types from Class or Type argument. int stackElementIndex = matchingMatcher.typeStackElementIndex == -1 ? matchingMatcher.classStackElementIndex : matchingMatcher.typeStackElementIndex; InstructionOffsetValue producer = partialEvaluator.getStackBefore(offset) .getTopActualProducerValue(stackElementIndex) .instructionOffsetValue(); TypeArgumentFinder typeArgumentFinder = new TypeArgumentFinder(programClassPool, libraryClassPool, partialEvaluator); for (int i = 0; i < producer.instructionOffsetCount(); i++) { codeAttribute.instructionAccept(clazz, method, producer.instructionOffset(i), typeArgumentFinder); } String[] targetTypes = typeArgumentFinder.typeArgumentClasses; if (targetTypes != null) { for (String targetType : targetTypes) { logger.debug("GsonDeserializationInvocationFinder: deserialized type: {}", targetType); programClassPool.classAccept(targetType, domainClassVisitor); } } else if (warningPrinter != null) { warningPrinter.print(clazz.getName(), "Warning: can't derive deserialized type from fromJson() invocation in " + clazz.getName() + "." + method.getName(clazz) + method.getDescriptor(clazz)); } } } // Utility classes. private static class FromJsonInvocationMatcher extends InstructionSequenceMatcher { private int classStackElementIndex; private int typeStackElementIndex; private FromJsonInvocationMatcher(Constant[] patternConstants, Instruction[] patternInstructions, int classStackElementIndex, int typeStackElementIndex) { super(patternConstants, patternInstructions); this.classStackElementIndex = classStackElementIndex; this.typeStackElementIndex = typeStackElementIndex; } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonDeserializationOptimizer.java ================================================ /* * 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.LogManager; import org.apache.logging.log4j.Logger; 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 Logger logger = LogManager.getLogger(GsonDeserializationOptimizer.class); 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 boolean optimizeConservatively; 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 optimizeConservatively specifies whether conservative * optimization should be applied * @param extraDataEntryNameMap the map that keeps track of injected * classes. */ public GsonDeserializationOptimizer(ClassPool programClassPool, ClassPool libraryClassPool, GsonRuntimeSettings gsonRuntimeSettings, OptimizedJsonInfo deserializationInfo, boolean optimizeConservatively, ExtraDataEntryNameMap extraDataEntryNameMap) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.gsonRuntimeSettings = gsonRuntimeSettings; this.deserializationInfo = deserializationInfo; this.supportExposeAnnotation = gsonRuntimeSettings.excludeFieldsWithoutExposeAnnotation; this.optimizeConservatively = optimizeConservatively; 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) { logger.debug("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(false, optimizeConservatively)); } private void addFromJsonMethod(ProgramClass programClass, ClassBuilder classBuilder, int classIndex) { String methodNameFromJson = METHOD_NAME_FROM_JSON + classIndex; logger.debug("GsonDeserializationOptimizer: adding {} method to {}", methodNameFromJson, 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(false, optimizeConservatively)); } private void addFromJsonFieldMethod(ProgramClass programClass, ClassBuilder classBuilder, int classIndex) { String methodNameFromJsonField = METHOD_NAME_FROM_JSON_FIELD + classIndex; logger.debug("GsonDeserializationOptimizer: adding {} method to {}", methodNameFromJsonField, 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(false, optimizeConservatively)); } 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; } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonDomainClassFinder.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.annotation.Annotation; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.util.ProcessingFlags; import java.util.*; /** * This class visitor determines whether a given domain class can be optimized * by the GSON optimizations and traverses both the class and field hierarchy * to look for further domain classes. * * @author Lars Vandenbergh */ public class GsonDomainClassFinder implements ClassVisitor { private static final Logger logger = LogManager.getLogger(GsonDomainClassFinder.class); //* public static final boolean DEBUG = false; /*/ public static boolean DEBUG = System.getProperty("gdcf") != null; //*/ private final GsonRuntimeSettings gsonRuntimeSettings; private final ClassPool gsonDomainClassPool; private final WarningPrinter warningPrinter; private final ClassPool unoptimizedClassPool = new ClassPool(); private final LocalOrAnonymousClassChecker localOrAnonymousClassChecker = new LocalOrAnonymousClassChecker(); private final TypeParameterClassChecker typeParameterClassChecker = new TypeParameterClassChecker(); private final DuplicateJsonFieldNameChecker duplicateFieldNameChecker = new DuplicateJsonFieldNameChecker(); private Queue toProcess = new ArrayDeque<>(); /** * Creates a new GsonDomainClassFinder. * * @param gsonRuntimeSettings keeps track of all GsonBuilder invocations. * @param gsonDomainClassPool the class pool to which the found domain * classes are added. * @param warningPrinter used to print notes about domain classes that * can not be handled by the Gson optimization. */ public GsonDomainClassFinder(GsonRuntimeSettings gsonRuntimeSettings, ClassPool gsonDomainClassPool, WarningPrinter warningPrinter) { this.gsonRuntimeSettings = gsonRuntimeSettings; this.gsonDomainClassPool = gsonDomainClassPool; this.warningPrinter = warningPrinter; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { toProcess.add(new ClassAnalysisCommand(programClass, true)); // For classes that are Gson "seeds" (they immediately occur in Gson // invocations or are a field of another Gson domain class), we also // want to visit the super and sub classes in the class hierarchy. ClassVisitor recursiveVisitor = new HierarchyClassVisitor(true); ClassVisitor hierarchyClassVisitor = new HierarchyClassVisitor(false); while(!toProcess.isEmpty()) { ClassAnalysisCommand toAnalyse = toProcess.poll(); handleDomainClass(toAnalyse.programClass, recursiveVisitor, toAnalyse.recursive ? hierarchyClassVisitor : null); } } // Utility methods. private void handleDomainClass(ProgramClass programClass, ClassVisitor recursiveVisitor, ClassVisitor hierarchyClassVisitor) { if (gsonDomainClassPool.getClass(programClass.getName()) == null && unoptimizedClassPool.getClass(programClass.getName()) == null) { // Local or anonymous classes are excluded by GSON. programClass.accept(localOrAnonymousClassChecker); if (localOrAnonymousClassChecker.isLocalOrAnonymous()) { // No need to note here because this is not handled // by GSON either. return; } if(librarySuperClassCount(programClass) != 0) { note(programClass, "Warning: " + ClassUtil.externalClassName(programClass.getName() + " can not be optimized for GSON because" + " it is or inherits from a library class.")); return; } if(gsonSuperClassCount(programClass) != 0) { note(programClass, "Warning: " + ClassUtil.externalClassName(programClass.getName() + " can not be optimized for GSON because" + " it is or inherits from a GSON API class.")); return; } // Classes with fields that have generic type parameters are // not supported by our optimization as it is rather complex // to derive all possible type arguments and generate methods // for each case. typeParameterClassChecker.hasFieldWithTypeParameter = false; programClass.hierarchyAccept(true, true, false, false, typeParameterClassChecker); if (typeParameterClassChecker.hasFieldWithTypeParameter) { note(programClass, "Warning: " + ClassUtil.externalClassName(programClass.getName() + " can not be optimized for GSON because" + " it uses generic type variables.")); return; } // Class with duplicate field names are not supported by // GSON either. duplicateFieldNameChecker.hasDuplicateJsonFieldNames = false; programClass.hierarchyAccept(true, true, false, false, duplicateFieldNameChecker); if (duplicateFieldNameChecker.hasDuplicateJsonFieldNames) { note(programClass, "Warning: " + ClassUtil.externalClassName(programClass.getName() + " can not be optimized for GSON because" + " it contains duplicate field names in its JSON representation.")); return; } // Classes for which type adapters were registered are not optimized. ClassCounter typeAdapterClassCounter = new ClassCounter(); programClass.hierarchyAccept(true, true, false, false, new ClassPresenceFilter( gsonRuntimeSettings.typeAdapterClassPool, typeAdapterClassCounter, null)); if (typeAdapterClassCounter.getCount() > 0) { note(programClass, "Warning: " + ClassUtil.externalClassName(programClass.getName() + " can not be optimized for GSON because" + " a custom type adapter is registered for it.")); return; } // Classes that contain any JsonAdapter annotations are not optimized. AnnotationFinder annotationFinder = new AnnotationFinder(); programClass.hierarchyAccept(true, true, false, false, new MultiClassVisitor( new AllAttributeVisitor(true, new AllAnnotationVisitor( new AnnotationTypeFilter(GsonClassConstants.ANNOTATION_TYPE_JSON_ADAPTER, annotationFinder))))); if (annotationFinder.found) { note(programClass, "Warning: " + ClassUtil.externalClassName(programClass.getName() + " can not be optimized for GSON because" + " it contains a JsonAdapter annotation.")); return; } if ((programClass.getAccessFlags() & AccessConstants.INTERFACE) == 0) { logger.debug("GsonDomainClassFinder: adding domain class {}", programClass.getName() ); // Add type occurring in toJson() invocation to domain class pool. gsonDomainClassPool.addClass(programClass); // Recursively visit the fields of the domain class and consider // their classes as domain classes too. int requiredUnsetAccessFlags = AccessConstants.SYNTHETIC; if (!gsonRuntimeSettings.excludeFieldsWithModifiers) { // If fields are not excluded based on modifiers, we assume // the default behavior of Gson, which is to exclude // transient and static fields. requiredUnsetAccessFlags |= AccessConstants.TRANSIENT | AccessConstants.STATIC; } programClass.fieldsAccept( new MemberAccessFilter(0, requiredUnsetAccessFlags, new MultiMemberVisitor( new MemberDescriptorReferencedClassVisitor(recursiveVisitor), new AllAttributeVisitor( new SignatureAttributeReferencedClassVisitor(recursiveVisitor))))); } // Consider super and sub classes as domain classes too, except for // sub classes of enum types that are generated by the compiler // but don't contain any additional fields serialized by Gson. if (hierarchyClassVisitor != null && (programClass.getAccessFlags() & AccessConstants.ENUM) == 0) { programClass.hierarchyAccept(false, true, false, true, hierarchyClassVisitor); } } } private int librarySuperClassCount(ProgramClass programClass) { ClassCounter nonObjectLibrarySuperClassCounter = new ClassCounter(); programClass.hierarchyAccept(true, true, false, false, new LibraryClassFilter( new ClassNameFilter(Arrays.asList("!java/lang/Object", "!java/lang/Enum"), nonObjectLibrarySuperClassCounter))); return nonObjectLibrarySuperClassCounter.getCount(); } private int gsonSuperClassCount(ProgramClass programClass) { ClassCounter gsonSuperClassCounter = new ClassCounter(); programClass.hierarchyAccept(true, true, false, false, new ProgramClassFilter( new ClassNameFilter("com/google/gson/**", gsonSuperClassCounter))); return gsonSuperClassCounter.getCount(); } private void note(ProgramClass programClass, String note) { if (warningPrinter != null && !isKept(programClass)) { warningPrinter.print(programClass.getName(), note); warningPrinter.print(programClass.getName(), " You should consider keeping this class and its members in your configuration " + "as follows:"); warningPrinter.print(programClass.getName(), " -keep class " + ClassUtil.externalClassName(programClass.getName()) + " { *; }"); } unoptimizedClassPool.addClass(programClass); } private boolean isKept(ProgramClass programClass) { if ((programClass.getProcessingFlags() & ProcessingFlags.DONT_SHRINK) != 0 && (programClass.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0) { UnkeptFieldFinder unkeptFieldFinder = new UnkeptFieldFinder(); programClass.fieldsAccept(unkeptFieldFinder); return !unkeptFieldFinder.found; } return false; } private class HierarchyClassVisitor implements ClassVisitor { private final boolean recursive; private HierarchyClassVisitor(boolean recursive) { this.recursive = recursive; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { toProcess.add(new ClassAnalysisCommand(programClass, recursive)); } } private class AnnotationFinder implements AnnotationVisitor { private boolean found; // Implementations for AnnotationVisitor. @Override public void visitAnnotation(Clazz clazz, Annotation annotation) { found = true; } } private class UnkeptFieldFinder implements MemberVisitor { private boolean found; // Implementations for MemberVisitor. @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { found = found || !isKept(programField); } // Utility methods. private boolean isKept(ProgramField programField) { return (programField.getProcessingFlags() & ProcessingFlags.DONT_SHRINK) != 0 && (programField.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0; } } private static class ClassAnalysisCommand { public final ProgramClass programClass; public final boolean recursive; private ClassAnalysisCommand(ProgramClass programClass, boolean recursive) { this.programClass = programClass; this.recursive = recursive; } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonInstrumentationAdder.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import static proguard.classfile.instruction.Instruction.OP_ARETURN; import static proguard.classfile.instruction.Instruction.OP_RETURN; /** * Instruction visitor that adds some instrumentation code to the Gson.toJson() * and Gson.fromJson() methods that prints out the type adapter cache. This * can be useful for debugging purposes. * * @author Lars Vandenbergh */ public class GsonInstrumentationAdder implements InstructionVisitor { private static final Logger logger = LogManager.getLogger(GsonInstrumentationAdder.class); private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final CodeAttributeEditor codeAttributeEditor; /** * Creates a new GsonInstrumentationAdder. * * @param programClassPool the program class pool used for looking up * program class references. * @param libraryClassPool the library class pool used for looking up * library class references. * @param codeAttributeEditor the code attribute editor used for editing * the code attribute of the Gson methods. */ public GsonInstrumentationAdder(ClassPool programClassPool, ClassPool libraryClassPool, CodeAttributeEditor codeAttributeEditor) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.codeAttributeEditor = codeAttributeEditor; } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { if (instruction.actualOpcode() == OP_RETURN || instruction.actualOpcode() == OP_ARETURN) { String fullyQualifiedMethodName = clazz.getName() + "#" + method.getName(clazz) + method.getDescriptor(clazz); logger.debug("GsonInstrumentationAdder: instrumenting {}", fullyQualifiedMethodName); InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz, programClassPool, libraryClassPool); ____.ldc("Type token cache after invoking " + fullyQualifiedMethodName + ":") .aload_0() .getfield(clazz.getName(), GsonClassConstants.FIELD_NAME_TYPE_TOKEN_CACHE, GsonClassConstants.FIELD_TYPE_TYPE_TOKEN_CACHE) .invokestatic(OptimizedClassConstants.NAME_GSON_UTIL, OptimizedClassConstants.METHOD_NAME_DUMP_TYPE_TOKEN_CACHE, OptimizedClassConstants.METHOD_TYPE_DUMP_TYPE_TOKEN_CACHE); codeAttributeEditor.insertBeforeInstruction(offset, ____.instructions()); } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonOptimizer.java ================================================ /* * 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.AppView; 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.pass.Pass; 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 pass 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 implements Pass { 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 }; private final Configuration configuration; public GsonOptimizer(Configuration configuration) { this.configuration = configuration; } /** * Performs the Gson optimizations. * * @throws IOException when the injected template classes cannot be read. */ @Override public void execute(AppView appView) throws IOException { // Do we have Gson code? if (appView.programClassPool.getClass("com/google/gson/Gson") == null) { return; } logger.info("Optimizing usages of Gson library..."); // Set all fields of Gson to public. appView.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. appView.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(appView.programClassPool, appView.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(appView.programClassPool, null, new ClassPoolFiller(appView.programClassPool)), new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool), new ClassSubHierarchyInitializer())); for (String clazz : TEMPLATE_CLASSES) { helperClassReader.read(new ClassPathDataEntry(clazz + CLASS_FILE_EXTENSION)); for (Clazz domainClass : gsonContext.gsonDomainClassPool.classes()) { appView.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); appView.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(appView.programClassPool, appView.libraryClassPool, codeAttributeEditor, serializationInfo))))))); appView.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(appView.programClassPool, appView.libraryClassPool, codeAttributeEditor, deserializationInfo))))))); // Inject serialization and deserialization code in domain classes. gsonContext.gsonDomainClassPool .classesAccept(new ClassAccessFilter(0, AccessConstants.ENUM, new GsonSerializationOptimizer(appView.programClassPool, appView.libraryClassPool, gsonContext.gsonRuntimeSettings, serializationInfo, configuration.optimizeConservatively, appView.extraDataEntryNameMap))); gsonContext.gsonDomainClassPool .classesAccept(new ClassAccessFilter(0, AccessConstants.ENUM, new GsonDeserializationOptimizer(appView.programClassPool, appView.libraryClassPool, gsonContext.gsonRuntimeSettings, deserializationInfo, configuration.optimizeConservatively, appView.extraDataEntryNameMap))); gsonContext.gsonDomainClassPool .classesAccept(new ClassReferenceInitializer(appView.programClassPool, appView.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(appView.programClassPool, appView.libraryClassPool, codeAttributeEditor, serializationInfo, deserializationInfo, appView.extraDataEntryNameMap, typeAdapterRegistry, gsonContext.gsonRuntimeSettings); gsonContext.gsonDomainClassPool.classesAccept( new ClassAccessFilter(0, AccessConstants.ABSTRACT, optimizedTypeAdapterAdder)); // Implement type adapter factory. appView.programClassPool.classAccept(NAME_OPTIMIZED_TYPE_ADAPTER_FACTORY, new MultiClassVisitor( new AllMemberVisitor( new AllAttributeVisitor( new PeepholeEditor(branchTargetFinder, codeAttributeEditor, new OptimizedTypeAdapterFactoryInitializer(appView.programClassPool, codeAttributeEditor, typeAdapterRegistry, gsonContext.gsonRuntimeSettings)))), new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool))); // Add excluder field to Gson class if not present to support // @Expose in earlier Gson versions (down to 2.1). ProgramClass gsonClass = (ProgramClass) appView.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, appView.programClassPool, appView.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(appView.programClassPool, appView.libraryClassPool)); gsonClass.constantPoolEntriesAccept(new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool)); } // Inject code that registers inject type adapter factory for optimized domain classes in Gson constructor. appView.programClassPool.classAccept(NAME_GSON, new MultiClassVisitor( new AllMemberVisitor( new MemberNameFilter(ClassConstants.METHOD_NAME_INIT, new GsonConstructorPatcher(codeAttributeEditor, addExcluder))), new ClassReferenceInitializer(appView.programClassPool, appView.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(). appView.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(appView.programClassPool, appView.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(appView.programClassPool, appView.libraryClassPool, codeAttributeEditor)))))))); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonRuntimeSettings.java ================================================ /* * 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.ClassPool; /** * This class keeps track of which parameters of the GsonBuilder are being * utilized in the code. * * @author Lars Vandenbergh */ public class GsonRuntimeSettings { public boolean setVersion; public boolean excludeFieldsWithModifiers; public boolean generateNonExecutableJson; public boolean excludeFieldsWithoutExposeAnnotation; public boolean serializeNulls; // This setting is taken care of by the built-in MapTypeAdapterFactory // of Gson. // public boolean enableComplexMapKeySerialization; public boolean disableInnerClassSerialization; public boolean setLongSerializationPolicy; public boolean setFieldNamingPolicy; public boolean setFieldNamingStrategy; public boolean setExclusionStrategies; public boolean addSerializationExclusionStrategy; public boolean addDeserializationExclusionStrategy; // These settings on the builder are taken care of by the JsonWriter and // JsonReader and don't affect our optimizations. // public boolean setPrettyPrinting; // public boolean setLenient; // public boolean disableHtmlEscaping; // This setting is taken care of by the built-in DateTypeAdapters of Gson. // public boolean setDateFormat; // These are the classes for which an instance creator or type adapter was // register using either registerTypeAdapter() or // registerTypeHierarchyAdapter() public ClassPool instanceCreatorClassPool = new ClassPool(); public ClassPool typeAdapterClassPool = new ClassPool(); // These type adapter factories come before the _OptimizedTypeAdapterFactory // we inject. public boolean registerTypeAdapterFactory; public boolean serializeSpecialFloatingPointValues; } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonSerializationInvocationFinder.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.Constant; import proguard.classfile.editor.InstructionSequenceBuilder; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.evaluation.*; import proguard.evaluation.value.*; /** * This instruction visitor searches the code for invocations to any of the * serialization methods of Gson (all the toJson variants) and keeps * track of the domain classes that are involved in the serialization. * * @author Lars Vandenbergh */ public class GsonSerializationInvocationFinder implements InstructionVisitor { private static final Logger logger = LogManager.getLogger(GsonSerializationInvocationFinder.class); private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final ClassVisitor domainClassVisitor; private final WarningPrinter warningPrinter; private final ToJsonInvocationMatcher[] toJsonInvocationMatchers; private final TypedReferenceValueFactory valueFactory = new TypedReferenceValueFactory(); private final PartialEvaluator partialEvaluator = new PartialEvaluator(valueFactory, new BasicInvocationUnit(new TypedReferenceValueFactory()), true); private final AttributeVisitor lazyPartialEvaluator = new AttributeNameFilter(Attribute.CODE, new SingleTimeAttributeVisitor(partialEvaluator)); /** * Creates a new GsonSerializationInvocationFinder. * * @param programClassPool the program class pool used to look up class * references. * @param libraryClassPool the library class pool used to look up class * references. * @param domainClassVisitor the visitor to which found domain classes that * are involved in Gson serialization will * be delegated. * @param warningPrinter used to print warnings about domain classes that * can not be handled by the Gson optimization. */ public GsonSerializationInvocationFinder(ClassPool programClassPool, ClassPool libraryClassPool, ClassVisitor domainClassVisitor, WarningPrinter warningPrinter) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.domainClassVisitor = domainClassVisitor; this.warningPrinter = warningPrinter; // Create matchers for relevant instruction sequences. InstructionSequenceBuilder builder = new InstructionSequenceBuilder(); // The invocation "Gson#toJson(Object)". Instruction[] toJsonObjectInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_TO_JSON, GsonClassConstants.METHOD_TYPE_TO_JSON_OBJECT) .instructions(); // The invocation "Gson#toJson(Object, Type)". Instruction[] toJsonObjectTypeInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_TO_JSON, GsonClassConstants.METHOD_TYPE_TO_JSON_OBJECT_TYPE) .instructions(); // The invocation "Gson#toJson(Object, Appendable)". Instruction[] toJsonObjectAppendableInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_TO_JSON, GsonClassConstants.METHOD_TYPE_TO_JSON_OBJECT_APPENDABLE) .instructions(); // The invocation "Gson#toJson(Object, Type, Appendable)". Instruction[] toJsonObjectTypeAppendableInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_TO_JSON, GsonClassConstants.METHOD_TYPE_TO_JSON_OBJECT_TYPE_APPENDABLE) .instructions(); // The invocation "Gson#toJson(Object, Type, JsonWriter)". Instruction[] toJsonObjectTypeWriterInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_TO_JSON, GsonClassConstants.METHOD_TYPE_TO_JSON_OBJECT_TYPE_WRITER) .instructions(); // The invocation "Gson#toJsonTree(Object)". Instruction[] toJsonTreeObjectInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_TO_JSON_TREE, GsonClassConstants.METHOD_TYPE_TO_JSON_TREE_OBJECT) .instructions(); // The invocation "Gson#toJsonTree(Object, Type)". Instruction[] toJsonTreeObjectTypeInstructions = builder .invokevirtual(GsonClassConstants.NAME_GSON, GsonClassConstants.METHOD_NAME_TO_JSON_TREE, GsonClassConstants.METHOD_TYPE_TO_JSON_TREE_OBJECT_TYPE) .instructions(); Constant[] constants = builder.constants(); toJsonInvocationMatchers = new ToJsonInvocationMatcher[] { new ToJsonInvocationMatcher(constants, toJsonObjectInstructions , 0, -1), new ToJsonInvocationMatcher(constants, toJsonObjectTypeInstructions , 1, 0), new ToJsonInvocationMatcher(constants, toJsonObjectAppendableInstructions , 1, -1), new ToJsonInvocationMatcher(constants, toJsonObjectTypeAppendableInstructions, 2, 1), new ToJsonInvocationMatcher(constants, toJsonObjectTypeWriterInstructions , 2, 1), new ToJsonInvocationMatcher(constants, toJsonTreeObjectInstructions , 0, -1), new ToJsonInvocationMatcher(constants, toJsonTreeObjectTypeInstructions , 1, 0) }; } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // Try to match any of the toJson() constructs. ToJsonInvocationMatcher matchingMatcher = null; for (ToJsonInvocationMatcher matcher : toJsonInvocationMatchers) { instruction.accept(clazz, method, codeAttribute, offset, matcher); if(matcher.isMatching()) { matchingMatcher = matcher; break; } } if (matchingMatcher != null) { logger.debug("GsonSerializationInvocationFinder: Gson#toJson/toJsonTree: {}.{}{} {}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), instruction.toString(offset) ); // Figure out the type that is being serialized. lazyPartialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); if (matchingMatcher.typeStackElementIndex == -1) { // Derive type from Object argument. int stackElementIndex = matchingMatcher.objectStackElementIndex; ReferenceValue top = partialEvaluator.getStackBefore(offset) .getTop(stackElementIndex) .referenceValue(); Clazz targetClass = top.getReferencedClass(); if (targetClass instanceof ProgramClass) { logger.debug("GsonSerializationInvocationFinder: serialized type: {}", targetClass.getName()); targetClass.accept(domainClassVisitor); } } else { // Derive types from Type argument. int stackElementIndex = matchingMatcher.typeStackElementIndex; InstructionOffsetValue producer = partialEvaluator.getStackBefore(offset) .getTopActualProducerValue(stackElementIndex) .instructionOffsetValue(); TypeArgumentFinder typeArgumentFinder = new TypeArgumentFinder(programClassPool, libraryClassPool, partialEvaluator); for (int i = 0; i < producer.instructionOffsetCount(); i++) { codeAttribute.instructionAccept(clazz, method, producer.instructionOffset(i), typeArgumentFinder); } String[] targetTypes = typeArgumentFinder.typeArgumentClasses; if (targetTypes != null) { for (String targetType : targetTypes) { logger.debug("GsonSerializationInvocationFinder: serialized type: {}", targetType); programClassPool.classAccept(targetType, domainClassVisitor); } } else if (warningPrinter != null) { warningPrinter.print(clazz.getName(), "Warning: can't derive serialized type from toJson() invocation in " + clazz.getName() + "." + method.getName(clazz) + method.getDescriptor(clazz)); } } } } // Utility classes. private static class ToJsonInvocationMatcher extends InstructionSequenceMatcher { private int objectStackElementIndex; private int typeStackElementIndex; private ToJsonInvocationMatcher(Constant[] patternConstants, Instruction[] patternInstructions, int objectStackElementIndex, int typeStackElementIndex) { super(patternConstants, patternInstructions); this.objectStackElementIndex = objectStackElementIndex; this.typeStackElementIndex = typeStackElementIndex; } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/GsonSerializationOptimizer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.annotation.visitor.AnnotationVisitor; import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor; 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.ClassVisitor; import proguard.classfile.visitor.MemberAccessFilter; import proguard.classfile.visitor.MemberVisitor; import proguard.io.ExtraDataEntryNameMap; import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; import java.util.HashMap; import java.util.Map; import static proguard.optimize.gson.OptimizedClassConstants.*; /** * This visitor injects a toJson$xxx() method into the classes that it visits * that serializes its fields to Json. * * @author Lars Vandenbergh * @author Rob Coekaerts */ public class GsonSerializationOptimizer implements MemberVisitor, ClassVisitor, ElementValueVisitor, AttributeVisitor, AnnotationVisitor { private static final Logger logger = LogManager.getLogger(GsonSerializationOptimizer.class); private static final int VALUE_VARIABLE_INDEX = ClassUtil.internalMethodParameterSize(METHOD_TYPE_TO_JSON_BODY, false); private static final Map inlineSerializers = new HashMap(); private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final GsonRuntimeSettings gsonRuntimeSettings; // private final CodeAttributeEditor codeAttributeEditor; private final OptimizedJsonInfo serializationInfo; private final boolean supportExposeAnnotation; private final boolean optimizeConservatively; private final ExtraDataEntryNameMap extraDataEntryNameMap; // private InstructionSequenceBuilder ____; static { inlineSerializers.put(TypeConstants.BOOLEAN + "", new InlineSerializers.InlinePrimitiveBooleanSerializer()); inlineSerializers.put(ClassConstants.TYPE_JAVA_LANG_BOOLEAN, new InlineSerializers.InlineBooleanSerializer()); inlineSerializers.put(TypeConstants.BYTE + "", new InlineSerializers.InlinePrimitiveIntegerSerializer()); inlineSerializers.put(TypeConstants.SHORT + "", new InlineSerializers.InlinePrimitiveIntegerSerializer()); inlineSerializers.put(TypeConstants.INT + "", new InlineSerializers.InlinePrimitiveIntegerSerializer()); inlineSerializers.put(ClassConstants.TYPE_JAVA_LANG_STRING, new InlineSerializers.InlineStringSerializer()); } /** * Creates a new GsonSerializationOptimizer. * * @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 serializationInfo contains information on which class * and fields need to be optimized and how. * @param optimizeConservatively specifies whether conservative * optimization should be applied * @param extraDataEntryNameMap the map that keeps track of injected * classes. */ public GsonSerializationOptimizer(ClassPool programClassPool, ClassPool libraryClassPool, GsonRuntimeSettings gsonRuntimeSettings, OptimizedJsonInfo serializationInfo, boolean optimizeConservatively, ExtraDataEntryNameMap extraDataEntryNameMap) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.gsonRuntimeSettings = gsonRuntimeSettings; this.serializationInfo = serializationInfo; this.supportExposeAnnotation = gsonRuntimeSettings.excludeFieldsWithoutExposeAnnotation; this.optimizeConservatively = optimizeConservatively; this.extraDataEntryNameMap = extraDataEntryNameMap; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Make access public for _OptimizedTypeAdapterFactory. programClass.u2accessFlags &= ~AccessConstants.PRIVATE; programClass.u2accessFlags |= AccessConstants.PUBLIC; // Start adding new serialization methods. ClassBuilder classBuilder = new ClassBuilder(programClass); // Add toJson$ method. Integer classIndex = serializationInfo.classIndices.get(programClass.getName()); String methodNameToJson = METHOD_NAME_TO_JSON + classIndex; String methodNameToJsonBody = METHOD_NAME_TO_JSON_BODY + classIndex; logger.debug("GsonSerializationOptimizer: adding {} method to {}", methodNameToJson, programClass.getName() ); classBuilder.addMethod( AccessConstants.PUBLIC | AccessConstants.SYNTHETIC, methodNameToJson, METHOD_TYPE_TO_JSON, 100, ____ -> ____ // Begin Json object. .aload(OptimizedClassConstants.ToJsonLocals.JSON_WRITER) .invokevirtual(GsonClassConstants.NAME_JSON_WRITER, GsonClassConstants.METHOD_NAME_WRITER_BEGIN_OBJECT, GsonClassConstants.METHOD_TYPE_WRITER_BEGIN_OBJECT) // Invoke toJsonBody$. .aload(OptimizedClassConstants.ToJsonLocals.THIS) .aload(OptimizedClassConstants.ToJsonLocals.GSON) .aload(OptimizedClassConstants.ToJsonLocals.JSON_WRITER) .aload(OptimizedClassConstants.ToJsonLocals.OPTIMIZED_JSON_WRITER) .invokevirtual(programClass.getName(), methodNameToJsonBody, METHOD_TYPE_TO_JSON_BODY) // End Json object. .aload(OptimizedClassConstants.ToJsonLocals.JSON_WRITER) .invokevirtual(GsonClassConstants.NAME_JSON_WRITER, GsonClassConstants.METHOD_NAME_WRITER_END_OBJECT, GsonClassConstants.METHOD_TYPE_WRITER_END_OBJECT) .return_(), new ProgramMemberOptimizationInfoSetter(false, optimizeConservatively)); addToJsonBodyMethod(programClass, classBuilder); // 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 addToJsonBodyMethod(ProgramClass programClass, ClassBuilder classBuilder) { Integer classIndex = serializationInfo.classIndices.get(programClass.getName()); String methodName = METHOD_NAME_TO_JSON_BODY + classIndex; // Add toJsonBody$ method. logger.debug("GsonSerializationOptimizer: adding {} method to {}", methodName, programClass.getName() ); classBuilder.addMethod( AccessConstants.PROTECTED | AccessConstants.SYNTHETIC, methodName, METHOD_TYPE_TO_JSON_BODY, 1000, ____ -> { // Apply non static member visitor to all fields to visit. programClass.fieldsAccept( new MemberAccessFilter(0, AccessConstants.SYNTHETIC | AccessConstants.STATIC, new ToJsonFieldSerializationCodeAdder(____))); if (programClass.getSuperClass() != null) { // Call the superclass toJsonBody$ if there is one. if (!programClass.getSuperClass().getName().equals(ClassConstants.NAME_JAVA_LANG_OBJECT)) { Integer superClassIndex = serializationInfo.classIndices.get(programClass.getSuperClass().getName()); String superMethodNameToJsonBody = METHOD_NAME_TO_JSON_BODY + superClassIndex; ____.aload(OptimizedClassConstants.ToJsonLocals.THIS) .aload(OptimizedClassConstants.ToJsonLocals.GSON) .aload(OptimizedClassConstants.ToJsonLocals.JSON_WRITER) .aload(OptimizedClassConstants.ToJsonLocals.OPTIMIZED_JSON_WRITER) .invokevirtual(programClass.getSuperClass().getName(), superMethodNameToJsonBody, METHOD_TYPE_TO_JSON_BODY); } } else { throw new RuntimeException ("Cannot find super class of " + programClass.getName() + " for Gson optimization. Please check your configuration includes all the expected library jars."); } ____.return_(); }, new ProgramMemberOptimizationInfoSetter(false, optimizeConservatively)); } private class ToJsonFieldSerializationCodeAdder implements MemberVisitor { private final CompactCodeAttributeComposer ____; public ToJsonFieldSerializationCodeAdder(CompactCodeAttributeComposer ____) { this.____ = ____; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { OptimizedJsonInfo.ClassJsonInfo classSerializationInfo = serializationInfo.classJsonInfos.get(programClass.getName()); String[] jsonFieldNames = classSerializationInfo.javaToJsonFieldNames.get(programField.getName(programClass)); String javaFieldName = programField.getName(programClass); if (jsonFieldNames != null) { // Derive field descriptor and signature (if it exists). String fieldDescriptor = programField.getDescriptor(programClass); FieldSignatureCollector signatureAttributeCollector = new FieldSignatureCollector(); programField.attributesAccept(programClass, signatureAttributeCollector); boolean retrieveAdapterByTypeToken = false; // Check for recursion first if it is an object CompactCodeAttributeComposer.Label end = ____.createLabel(); if(ClassUtil.isInternalClassType(fieldDescriptor)) { CompactCodeAttributeComposer.Label noRecursion = ____.createLabel(); ____.aload(OptimizedClassConstants.ToJsonLocals.THIS) .aload(OptimizedClassConstants.ToJsonLocals.THIS) .getfield(programClass, programField) .ifacmpne(noRecursion) .goto_(end) .label(noRecursion); } if (supportExposeAnnotation && !classSerializationInfo.exposedJavaFieldNames.contains(javaFieldName)) { ____.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(end); } // Write field name. Integer fieldIndex = serializationInfo.jsonFieldIndices.get(jsonFieldNames[0]); ____.aload(OptimizedClassConstants.ToJsonLocals.OPTIMIZED_JSON_WRITER) .aload(OptimizedClassConstants.ToJsonLocals.JSON_WRITER) .ldc(fieldIndex.intValue()) .invokeinterface(OptimizedClassConstants.NAME_OPTIMIZED_JSON_WRITER, OptimizedClassConstants.METHOD_NAME_NAME, OptimizedClassConstants.METHOD_TYPE_NAME); // Write field value. InlineSerializer inlineSerializer = inlineSerializers.get(fieldDescriptor); if (!gsonRuntimeSettings.registerTypeAdapterFactory && inlineSerializer != null && inlineSerializer.canSerialize(programClassPool, gsonRuntimeSettings)) { inlineSerializer.serialize(programClass, programField, ____, gsonRuntimeSettings); } else { // Write value to Json writer based on declared type and runtime value/type. ____.aload(OptimizedClassConstants.ToJsonLocals.GSON); switch (fieldDescriptor.charAt(0)) { case TypeConstants.BOOLEAN: case TypeConstants.CHAR: case TypeConstants.BYTE: case TypeConstants.SHORT: case TypeConstants.INT: case TypeConstants.FLOAT: case TypeConstants.LONG: case TypeConstants.DOUBLE: { String className = ClassUtil.internalNumericClassNameFromPrimitiveType(fieldDescriptor.charAt(0)); ____.getstatic(className, ClassConstants.FIELD_NAME_TYPE, ClassConstants.FIELD_TYPE_TYPE); break; } case TypeConstants.CLASS_START: { if (signatureAttributeCollector.getFieldSignature() == null) { String fieldClassName = fieldDescriptor.substring(1, fieldDescriptor.length() - 1); Clazz fieldClass = programClassPool.getClass(fieldClassName); if (fieldClass == null) { fieldClass = libraryClassPool.getClass(fieldClassName); } ____.ldc(fieldClassName, fieldClass); } else { // 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)); extraDataEntryNameMap.addExtraClassToClass(programClass.getName(), typeTokenClass.getName()); // Instantiate type token. ____.new_(typeTokenClass.getName()) .dup() .invokespecial(typeTokenClass.getName(), ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT); retrieveAdapterByTypeToken = true; } break; } case TypeConstants.ARRAY: { String elementType = ClassUtil.internalTypeFromArrayType(fieldDescriptor); Clazz fieldClass; if (ClassUtil.isInternalPrimitiveType(elementType)) { String primitiveClassName = ClassUtil.internalNumericClassNameFromPrimitiveType(elementType.charAt(0)); fieldClass = libraryClassPool.getClass(primitiveClassName); ____.ldc(fieldDescriptor, fieldClass); } else { String className = ClassUtil.internalClassNameFromClassType(elementType); fieldClass = programClassPool.getClass(className); if (fieldClass == null) { fieldClass = libraryClassPool.getClass(className); } ____.ldc(fieldDescriptor, fieldClass); } break; } } ____.aload(OptimizedClassConstants.ToJsonLocals.THIS) .getfield(programClass, programField); // Box primitive value before passing it to type adapter. switch (fieldDescriptor.charAt(0)) { case TypeConstants.BOOLEAN: ____.invokestatic(ClassConstants.NAME_JAVA_LANG_BOOLEAN, ClassConstants.METHOD_NAME_VALUE_OF, ClassConstants.METHOD_TYPE_VALUE_OF_BOOLEAN); break; case TypeConstants.CHAR: ____.invokestatic(ClassConstants.NAME_JAVA_LANG_CHARACTER, ClassConstants.METHOD_NAME_VALUE_OF, ClassConstants.METHOD_TYPE_VALUE_OF_CHAR); break; case TypeConstants.BYTE: ____.invokestatic(ClassConstants.NAME_JAVA_LANG_BYTE, ClassConstants.METHOD_NAME_VALUE_OF, ClassConstants.METHOD_TYPE_VALUE_OF_BYTE); break; case TypeConstants.SHORT: ____.invokestatic(ClassConstants.NAME_JAVA_LANG_SHORT, ClassConstants.METHOD_NAME_VALUE_OF, ClassConstants.METHOD_TYPE_VALUE_OF_SHORT); break; case TypeConstants.INT: ____.invokestatic(ClassConstants.NAME_JAVA_LANG_INTEGER, ClassConstants.METHOD_NAME_VALUE_OF, ClassConstants.METHOD_TYPE_VALUE_OF_INT); break; case TypeConstants.FLOAT: ____.invokestatic(ClassConstants.NAME_JAVA_LANG_FLOAT, ClassConstants.METHOD_NAME_VALUE_OF, ClassConstants.METHOD_TYPE_VALUE_OF_FLOAT); break; case TypeConstants.LONG: ____.invokestatic(ClassConstants.NAME_JAVA_LANG_LONG, ClassConstants.METHOD_NAME_VALUE_OF, ClassConstants.METHOD_TYPE_VALUE_OF_LONG); break; case TypeConstants.DOUBLE: ____.invokestatic(ClassConstants.NAME_JAVA_LANG_DOUBLE, ClassConstants.METHOD_NAME_VALUE_OF, ClassConstants.METHOD_TYPE_VALUE_OF_DOUBLE); break; } // Copy value to local. ____.dup() .astore(VALUE_VARIABLE_INDEX); // Retrieve type adapter. if(retrieveAdapterByTypeToken) { ____.invokestatic(OptimizedClassConstants.NAME_GSON_UTIL, OptimizedClassConstants.METHOD_NAME_GET_TYPE_ADAPTER_TYPE_TOKEN, OptimizedClassConstants.METHOD_TYPE_GET_TYPE_ADAPTER_TYPE_TOKEN); } else { ____.invokestatic(OptimizedClassConstants.NAME_GSON_UTIL, OptimizedClassConstants.METHOD_NAME_GET_TYPE_ADAPTER_CLASS, OptimizedClassConstants.METHOD_TYPE_GET_TYPE_ADAPTER_CLASS); } // Write value using type adapter. ____.aload(OptimizedClassConstants.ToJsonLocals.JSON_WRITER) .aload(VALUE_VARIABLE_INDEX) .invokevirtual(GsonClassConstants.NAME_TYPE_ADAPTER, GsonClassConstants.METHOD_NAME_WRITE, GsonClassConstants.METHOD_TYPE_WRITE); } // Label for skipping writing of field in case of recursion or // a non-exposed field with excludeFieldsWithoutExposeAnnotation // enabled. ____.label(end); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/InlineDeserializer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.editor.*; /** * Interface for injecting optimized code for deserializing a field of a class * from Json. * * @author Lars Vandenbergh */ interface InlineDeserializer { /** * Indicates whether the deserializer can inject optimized code given which * GSON builder invocations are utilized in the program code. * * @param gsonRuntimeSettings tracks the GSON parameters that are utilized * in the code. * @return true if and only if the deserializer can inject optimized code. */ boolean canDeserialize(GsonRuntimeSettings gsonRuntimeSettings); /** * Appends optimized code for deserializing the given field of the given class * using the given code attribute editor and instruction sequence builder. * * The current locals are: * 0 this (the domain object) * 1 gson * 2 jsonReader * 3 fieldIndex * * @param programClass The domain class containing the field to * deserialize. * @param programField The field of the domain class to * deserialize. * @param composer the composer to be used for adding * instructions. * @param gsonRuntimeSettings tracks the GSON parameters that are utilized * in the code. */ void deserialize(ProgramClass programClass, ProgramField programField, CompactCodeAttributeComposer composer, GsonRuntimeSettings gsonRuntimeSettings); } ================================================ FILE: base/src/main/java/proguard/optimize/gson/InlineDeserializers.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.editor.*; /** * Class that groups all InlineDeserializer implementations for common types * together. * * @author Lars Vandenbergh */ class InlineDeserializers { /** * Deserializer for handling primitive int, short and byte values. */ static class InlinePrimitiveIntegerDeserializer implements InlineDeserializer { private final Class targetType; public InlinePrimitiveIntegerDeserializer() { this(null); } public InlinePrimitiveIntegerDeserializer(Class targetType) { this.targetType = targetType; } // Implementations for InlineDeserializer. @Override public boolean canDeserialize(GsonRuntimeSettings gsonRuntimeSettings) { // Don't deserialize inline when a custom type adapter for Integer // is registered. return gsonRuntimeSettings .typeAdapterClassPool .getClass(ClassConstants.NAME_JAVA_LANG_INTEGER) == null; } @Override public void deserialize(ProgramClass programClass, ProgramField programField, CompactCodeAttributeComposer ____, GsonRuntimeSettings gsonRuntimeSettings) { // Create labels for exception table. CompactCodeAttributeComposer.Label tryStart = ____.createLabel(); CompactCodeAttributeComposer.Label tryEnd = ____.createLabel(); CompactCodeAttributeComposer.Label catchEnd = ____.createLabel(); // Try to read and parse integer. ____.label(tryStart) .aload(OptimizedClassConstants.FromJsonLocals.THIS) .aload(OptimizedClassConstants.FromJsonLocals.JSON_READER) .invokevirtual(GsonClassConstants.NAME_JSON_READER, GsonClassConstants.METHOD_NAME_NEXT_INTEGER, GsonClassConstants.METHOD_TYPE_NEXT_INTEGER); // Convert if necessary. if (targetType == byte.class) { ____.i2b(); } else if (targetType == short.class) { ____.i2s(); } // Assign it to the field ____.putfield(programClass, programField) .goto_(catchEnd) .label(tryEnd); // Throw JsonSyntaxException if reading and parsing the integer failed. int throwableLocal = OptimizedClassConstants.FromJsonLocals.MAX_LOCALS + 1; ____.catch_(tryStart, tryEnd, ClassConstants.NAME_JAVA_LANG_NUMBER_FORMAT_EXCEPTION, null) .astore(throwableLocal) .new_(GsonClassConstants.NAME_JSON_SYNTAX_EXCEPTION) .dup() .aload(throwableLocal) .invokespecial(GsonClassConstants.NAME_JSON_SYNTAX_EXCEPTION, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT_THROWABLE) .athrow() .label(catchEnd); } } /** * Deserializer for handling String values. */ static class InlineStringDeserializer implements InlineDeserializer { // Implementations for InlineDeserializer. @Override public boolean canDeserialize(GsonRuntimeSettings gsonRuntimeSettings) { // Don't deserialize inline when a custom type adapter for String is // registered. return gsonRuntimeSettings .typeAdapterClassPool .getClass(ClassConstants.NAME_JAVA_LANG_STRING) == null; } @Override public void deserialize(ProgramClass programClass, ProgramField programField, CompactCodeAttributeComposer ____, GsonRuntimeSettings gsonRuntimeSettings) { CompactCodeAttributeComposer.Label isBoolean = ____.createLabel(); CompactCodeAttributeComposer.Label end = ____.createLabel(); // Peek value and check whether it is a boolean. ____.aload(OptimizedClassConstants.FromJsonLocals.JSON_READER) .invokevirtual(GsonClassConstants.NAME_JSON_READER, GsonClassConstants.METHOD_NAME_PEEK, GsonClassConstants.METHOD_TYPE_PEEK) .getstatic(GsonClassConstants.NAME_JSON_TOKEN, GsonClassConstants.FIELD_NAME_BOOLEAN, GsonClassConstants.FIELD_TYPE_BOOLEAN) .ifacmpeq(isBoolean); // It's not a boolean, just read the String and assign it to the // field. ____.aload(OptimizedClassConstants.FromJsonLocals.THIS) .aload(OptimizedClassConstants.FromJsonLocals.JSON_READER) .invokevirtual(GsonClassConstants.NAME_JSON_READER, GsonClassConstants.METHOD_NAME_NEXT_STRING, GsonClassConstants.METHOD_TYPE_NEXT_STRING) .putfield(programClass, programField) .goto_(end); // It's a boolean, convert it to a String first and then assign it // to the field. ____.label(isBoolean) .aload(OptimizedClassConstants.FromJsonLocals.THIS) .aload(OptimizedClassConstants.FromJsonLocals.JSON_READER) .invokevirtual(GsonClassConstants.NAME_JSON_READER, GsonClassConstants.METHOD_NAME_NEXT_BOOLEAN, GsonClassConstants.METHOD_TYPE_NEXT_BOOLEAN) .invokestatic(ClassConstants.NAME_JAVA_LANG_BOOLEAN, ClassConstants.METHOD_NAME_TOSTRING, ClassConstants.METHOD_TYPE_TOSTRING_BOOLEAN) .putfield(programClass, programField) .label(end); } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/InlineSerializer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.editor.*; /** * Interface for injecting optimized code for serializing a field of a class * to Json. * * @author Lars Vandenbergh */ interface InlineSerializer { /** * Indicates whether the serializer can inject optimized code given which * GSON builder invocations are utilized in the program code. * * @param programClassPool The class pool containing the program classes. * @param gsonRuntimeSettings tracks the GSON parameters that are utilized * in the code. * @return true if and only if the serializer can inject optimized code. */ boolean canSerialize(ClassPool programClassPool, GsonRuntimeSettings gsonRuntimeSettings); /** * Appends optimized code for serializing the given field of the given class * using the given code attribute editor and instruction sequence builder. * * The current locals are: * 0 this (the domain object) * 1 gson * 2 jsonWriter * 3 optimizedJsonWriter * * @param programClass The domain class containing the field to * serialize. * @param programField The field of the domain class to serialize. * @param composer the composer to be used for adding * instructions. * @param gsonRuntimeSettings tracks the GSON parameters that are utilized * in the code. */ void serialize(ProgramClass programClass, ProgramField programField, CompactCodeAttributeComposer composer, GsonRuntimeSettings gsonRuntimeSettings); } ================================================ FILE: base/src/main/java/proguard/optimize/gson/InlineSerializers.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.editor.*; /** * Class that groups all InlineSerializer implementations for common types * together. * * @author Lars Vandenbergh */ class InlineSerializers { /** * Serializer for primitive boolean values. */ static class InlinePrimitiveBooleanSerializer implements InlineSerializer { // Implementations for InlineSerializer. @Override public boolean canSerialize(ClassPool programClassPool, GsonRuntimeSettings gsonRuntimeSettings) { // Don't serialize inline when a custom type adapter for Boolean is // registered. return gsonRuntimeSettings .typeAdapterClassPool .getClass(ClassConstants.NAME_JAVA_LANG_BOOLEAN) == null; } @Override public void serialize(ProgramClass programClass, ProgramField programField, CompactCodeAttributeComposer ____, GsonRuntimeSettings gsonRuntimeSettings) { ____.aload(OptimizedClassConstants.ToJsonLocals.JSON_WRITER) .aload(OptimizedClassConstants.ToJsonLocals.THIS) .getfield(programClass, programField) .invokevirtual(GsonClassConstants.NAME_JSON_WRITER, GsonClassConstants.METHOD_NAME_VALUE_BOOLEAN, GsonClassConstants.METHOD_TYPE_VALUE_BOOLEAN) .pop(); } } /** * Serializer for handling Boolean values. */ static class InlineBooleanSerializer implements InlineSerializer { // Implementations for InlineSerializer. @Override public boolean canSerialize(ClassPool programClassPool, GsonRuntimeSettings gsonRuntimeSettings) { // Don't serialize inline when a custom type adapter for Boolean is // registered. if (gsonRuntimeSettings .typeAdapterClassPool .getClass(ClassConstants.NAME_JAVA_LANG_BOOLEAN) != null) { return false; } // Check whether JsonWriter.value(Boolean) is present in the used // version of Gson (should be from Gson 2.7 onwards). Clazz jsonWriterClass = programClassPool.getClass(GsonClassConstants.NAME_JSON_WRITER); Method valueBooleanMethod = jsonWriterClass.findMethod(GsonClassConstants.METHOD_NAME_VALUE_BOOLEAN_OBJECT, GsonClassConstants.METHOD_TYPE_VALUE_BOOLEAN_OBJECT); return valueBooleanMethod != null; } @Override public void serialize(ProgramClass programClass, ProgramField programField, CompactCodeAttributeComposer ____, GsonRuntimeSettings gsonRuntimeSettings) { ____.aload(OptimizedClassConstants.ToJsonLocals.JSON_WRITER) .aload(OptimizedClassConstants.ToJsonLocals.THIS) .getfield(programClass, programField) .invokevirtual(GsonClassConstants.NAME_JSON_WRITER, GsonClassConstants.METHOD_NAME_VALUE_BOOLEAN_OBJECT, GsonClassConstants.METHOD_TYPE_VALUE_BOOLEAN_OBJECT) .pop(); } } /** * Serializer for handling primitive int, short and byte values. */ static class InlinePrimitiveIntegerSerializer implements InlineSerializer { // Implementations for InlineSerializer. @Override public boolean canSerialize(ClassPool programClassPool, GsonRuntimeSettings gsonRuntimeSettings) { // Don't serialize inline when a custom type adapter for Integer is // registered. return gsonRuntimeSettings .typeAdapterClassPool .getClass(ClassConstants.NAME_JAVA_LANG_INTEGER) == null; } @Override public void serialize(ProgramClass programClass, ProgramField programField, CompactCodeAttributeComposer ____, GsonRuntimeSettings gsonRuntimeSettings) { ____.aload(OptimizedClassConstants.ToJsonLocals.JSON_WRITER) .aload(OptimizedClassConstants.ToJsonLocals.THIS) .getfield(programClass, programField); ____.invokestatic(ClassConstants.NAME_JAVA_LANG_INTEGER, ClassConstants.METHOD_NAME_VALUE_OF, ClassConstants.METHOD_TYPE_VALUE_OF_INT); ____.invokevirtual(GsonClassConstants.NAME_JSON_WRITER, GsonClassConstants.METHOD_NAME_VALUE_NUMBER, GsonClassConstants.METHOD_TYPE_VALUE_NUMBER) .pop(); } } /** * Serializer for handling String values. */ static class InlineStringSerializer implements InlineSerializer { // Implementations for InlineSerializer. @Override public boolean canSerialize(ClassPool programClassPool, GsonRuntimeSettings gsonRuntimeSettings) { // Don't serialize inline when a custom type adapter for String is // registered. return gsonRuntimeSettings .typeAdapterClassPool .getClass(ClassConstants.NAME_JAVA_LANG_STRING) == null; } @Override public void serialize(ProgramClass programClass, ProgramField programField, CompactCodeAttributeComposer ____, GsonRuntimeSettings gsonRuntimeSettings) { ____.aload(OptimizedClassConstants.ToJsonLocals.JSON_WRITER) .aload(OptimizedClassConstants.ToJsonLocals.THIS) .getfield(programClass, programField) .invokevirtual(GsonClassConstants.NAME_JSON_WRITER, GsonClassConstants.METHOD_NAME_VALUE_STRING, GsonClassConstants.METHOD_TYPE_NAME_VALUE_STRING) .pop(); } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/LocalOrAnonymousClassChecker.java ================================================ /* * 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.InnerClassesInfo; import proguard.classfile.attribute.visitor.*; import proguard.classfile.visitor.ClassVisitor; /** * Checks whether a visited class is a local or anonymous inner class. * * @author Lars Vandenbergh */ class LocalOrAnonymousClassChecker implements ClassVisitor, InnerClassesInfoVisitor { private boolean localOrAnonymous; public boolean isLocalOrAnonymous() { return localOrAnonymous; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) {} @Override public void visitProgramClass(ProgramClass programClass) { localOrAnonymous = false; programClass.attributesAccept(new AllInnerClassesInfoVisitor(this)); } // Implementations for InnerClassesInfoVisitor. @Override public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { if (innerClassesInfo.u2innerClassIndex == ((ProgramClass)clazz).u2thisClass) { localOrAnonymous = innerClassesInfo.u2outerClassIndex == 0 || innerClassesInfo.u2innerNameIndex == 0; } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/MarkedAnnotationDeleter.java ================================================ /* * 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.Attribute; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.*; /** * This AttributeVisitor deletes annotations with the given object as * processingInfo on the attributes that it visits. * If deleting an annotation results in the corresponding annotation attribute * to be empty, that attribute will be deleted as well. * * @author Rob Coekaerts */ class MarkedAnnotationDeleter implements AttributeVisitor { // A processing info flag to indicate the annotation can be deleted. private final Object mark; /** * Creates a new MarkedAnnotationDeleter. * * @param mark the processing info used to recognize annotations that * need to be deleted. */ public MarkedAnnotationDeleter(Object mark) { this.mark = mark; } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Member member, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute) { cleanAnnotationsAttribute(clazz, member, runtimeVisibleAnnotationsAttribute, Attribute.RUNTIME_VISIBLE_ANNOTATIONS); } @Override public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Member member, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute) { cleanAnnotationsAttribute(clazz, member, runtimeInvisibleAnnotationsAttribute, Attribute.RUNTIME_INVISIBLE_ANNOTATIONS); } @Override public void visitRuntimeVisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute) { cleanParameterAnnotationsAttribute(clazz, method, runtimeVisibleParameterAnnotationsAttribute, Attribute.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS); } @Override public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute) { cleanParameterAnnotationsAttribute(clazz, method, runtimeInvisibleParameterAnnotationsAttribute, Attribute.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS); } @Override public void visitRuntimeVisibleTypeAnnotationsAttribute(Clazz clazz, Member member, RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute) { cleanAnnotationsAttribute(clazz, member, runtimeVisibleTypeAnnotationsAttribute, Attribute.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); } @Override public void visitRuntimeInvisibleTypeAnnotationsAttribute(Clazz clazz, Member member, RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute) { cleanAnnotationsAttribute(clazz, member, runtimeInvisibleTypeAnnotationsAttribute, Attribute.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); } // Utility methods private void cleanAnnotationsAttribute(Clazz clazz, Member member, AnnotationsAttribute attribute, String attributeName) { // Delete marked annotations. AnnotationsAttributeEditor annotationsAttributeEditor = new AnnotationsAttributeEditor(attribute); Annotation[] annotations = attribute.annotations; int index = 0; while (index < attribute.u2annotationsCount) { Annotation annotation = annotations[index]; if (annotation.getProcessingInfo() == mark) { // We do not increase the index here, as we are deleting this element and the next element // to look at will be at the same index. annotationsAttributeEditor.deleteAnnotation(index); } else { index++; } } // Delete attribute if no annotations are left. if (attribute.u2annotationsCount == 0) { AttributesEditor attributesEditor = new AttributesEditor((ProgramClass) clazz, (ProgramMember)member, false); attributesEditor.deleteAttribute(attributeName); } } private void cleanParameterAnnotationsAttribute(Clazz clazz, Member member, ParameterAnnotationsAttribute attribute, String attributeName) { // Delete marked annotations. ParameterAnnotationsAttributeEditor annotationsAttributeEditor = new ParameterAnnotationsAttributeEditor(attribute); boolean allEmpty = true; for (int parameterIndex = 0; parameterIndex < attribute.u1parametersCount; parameterIndex++) { Annotation[] annotations = attribute.parameterAnnotations[parameterIndex]; int index = 0; while (index < attribute.u2parameterAnnotationsCount[parameterIndex]) { Annotation annotation = annotations[index]; if (annotation.getProcessingInfo() == mark) { // We do not increase the index here, as we are deleting this element and the next element // to look at will be at the same index. annotationsAttributeEditor.deleteAnnotation(parameterIndex, index); } else { index++; } } if (attribute.u2parameterAnnotationsCount[parameterIndex] != 0) { allEmpty = false; } } // Delete attribute if all parameters have no annotations left. if (allEmpty) { AttributesEditor attributesEditor = new AttributesEditor((ProgramClass) clazz, (ProgramMember)member, false); attributesEditor.deleteAttribute(attributeName); } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/OptimizedClassConstants.java ================================================ /* * 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.util.ClassUtil; /** * Constants used in the injected GSON optimization classes. * * @author Lars Vandenbergh */ public class OptimizedClassConstants { // The class names contained in these strings will be mapped during // obfuscation by -adaptclassstrings. // We can't use .class.getName() for these template classes because it // would indirectly load Gson library classes which are not on the runtime // classpath of ProGuard. public static final String NAME_GSON_UTIL = "proguard/optimize/gson/_GsonUtil"; public static final String NAME_OPTIMIZED_JSON_READER = "proguard/optimize/gson/_OptimizedJsonReader"; public static final String NAME_OPTIMIZED_JSON_READER_IMPL = "proguard/optimize/gson/_OptimizedJsonReaderImpl"; public static final String NAME_OPTIMIZED_JSON_WRITER = "proguard/optimize/gson/_OptimizedJsonWriter"; public static final String NAME_OPTIMIZED_JSON_WRITER_IMPL = "proguard/optimize/gson/_OptimizedJsonWriterImpl"; public static final String NAME_OPTIMIZED_TYPE_ADAPTER = "proguard/optimize/gson/_OptimizedTypeAdapter"; public static final String NAME_OPTIMIZED_TYPE_ADAPTER_FACTORY = "proguard/optimize/gson/_OptimizedTypeAdapterFactory"; public static final String NAME_OPTIMIZED_TYPE_ADAPTER_IMPL = "proguard/optimize/gson/_OptimizedTypeAdapterImpl"; public static final String METHOD_NAME_INIT_NAMES_MAP = "a"; public static final String METHOD_TYPE_INIT_NAMES_MAP = "()Ljava/util/Map;"; public static final String METHOD_NAME_NEXT_FIELD_INDEX = "b"; public static final String METHOD_TYPE_NEXT_FIELD_INDEX = "(Lcom/google/gson/stream/JsonReader;)I"; public static final String METHOD_NAME_NEXT_VALUE_INDEX = "c"; public static final String METHOD_TYPE_NEXT_VALUE_INDEX = "(Lcom/google/gson/stream/JsonReader;)I"; public static final String METHOD_NAME_INIT_NAMES = "a"; public static final String METHOD_TYPE_INIT_NAMES = "()[Ljava/lang/String;"; public static final String METHOD_NAME_NAME = "b"; public static final String METHOD_TYPE_NAME = "(Lcom/google/gson/stream/JsonWriter;I)V"; public static final String METHOD_NAME_VALUE = "c"; public static final String METHOD_TYPE_VALUE = "(Lcom/google/gson/stream/JsonWriter;I)V"; public static final String FIELD_NAME_OPTIMIZED_JSON_READER_IMPL = "optimizedJsonReaderImpl"; public static final String FIELD_TYPE_OPTIMIZED_JSON_READER_IMPL = ClassUtil.internalTypeFromClassName(NAME_OPTIMIZED_JSON_READER_IMPL); public static final String FIELD_NAME_OPTIMIZED_JSON_WRITER_IMPL = "optimizedJsonWriterImpl"; public static final String FIELD_TYPE_OPTIMIZED_JSON_WRITER_IMPL = ClassUtil.internalTypeFromClassName(NAME_OPTIMIZED_JSON_WRITER_IMPL); public static final String METHOD_TYPE_INIT = "(Lcom/google/gson/Gson;" + ClassUtil.internalTypeFromClassName(NAME_OPTIMIZED_JSON_READER) + ClassUtil.internalTypeFromClassName(NAME_OPTIMIZED_JSON_WRITER) + ")V"; public static final String METHOD_NAME_CREATE = "create"; public static final String METHOD_TYPE_CREATE = "(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;"; public static final String TYPE_OPTIMIZED_TYPE_ADAPTER_IMPL = ClassUtil.internalTypeFromClassName(NAME_OPTIMIZED_TYPE_ADAPTER_IMPL); public static final String FIELD_NAME_GSON = "gson"; public static final String FIELD_TYPE_GSON = "Lcom/google/gson/Gson;"; public static final String FIELD_NAME_OPTIMIZED_JSON_READER = "optimizedJsonReader"; public static final String FIELD_TYPE_OPTIMIZED_JSON_READER = ClassUtil.internalTypeFromClassName(NAME_OPTIMIZED_JSON_READER); public static final String FIELD_NAME_OPTIMIZED_JSON_WRITER = "optimizedJsonWriter"; public static final String FIELD_TYPE_OPTIMIZED_JSON_WRITER = ClassUtil.internalTypeFromClassName(NAME_OPTIMIZED_JSON_WRITER); public static final String METHOD_NAME_READ = "read"; public static final String METHOD_NAME_WRITE = "write"; public static final String FIELD_NAME_EXCLUDER = "excluder"; public static final String FIELD_TYPE_EXCLUDER = "Lcom/google/gson/internal/Excluder;"; public static final String FIELD_NAME_REQUIRE_EXPOSE = "requireExpose"; public static final String FIELD_TYPE_REQUIRE_EXPOSE = "Z"; public static final class ReadLocals { public static final int THIS = 0; public static final int JSON_READER = 1; public static final int VALUE = 2; } public static final class WriteLocals { public static final int THIS = 0; public static final int JSON_WRITER = 1; public static final int VALUE = 2; } public static final String METHOD_NAME_GET_TYPE_ADAPTER_CLASS = "getTypeAdapter"; public static final String METHOD_TYPE_GET_TYPE_ADAPTER_CLASS = "(Lcom/google/gson/Gson;Ljava/lang/Class;Ljava/lang/Object;)Lcom/google/gson/TypeAdapter;"; public static final String METHOD_NAME_GET_TYPE_ADAPTER_TYPE_TOKEN = "getTypeAdapter"; public static final String METHOD_TYPE_GET_TYPE_ADAPTER_TYPE_TOKEN = "(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;Ljava/lang/Object;)Lcom/google/gson/TypeAdapter;"; public static final String METHOD_NAME_DUMP_TYPE_TOKEN_CACHE = "dumpTypeTokenCache"; public static final String METHOD_TYPE_DUMP_TYPE_TOKEN_CACHE = "(Ljava/lang/String;Ljava/util/Map;)V"; public static final String METHOD_NAME_FROM_JSON = "fromJson$"; public static final String METHOD_TYPE_FROM_JSON = "(Lcom/google/gson/Gson;Lcom/google/gson/stream/JsonReader;" + ClassUtil.internalTypeFromClassName(NAME_OPTIMIZED_JSON_READER) + ")V"; public static final String METHOD_NAME_FROM_JSON_FIELD = "fromJsonField$"; public static final String METHOD_TYPE_FROM_JSON_FIELD = "(Lcom/google/gson/Gson;Lcom/google/gson/stream/JsonReader;I)V"; public static final class FromJsonLocals { public static final int THIS = 0; public static final int GSON = 1; public static final int JSON_READER = 2; public static final int OPTIMIZED_JSON_READER = 3; public static final int MAX_LOCALS = 3; } public static final class FromJsonFieldLocals { public static final int THIS = 0; public static final int GSON = 1; public static final int JSON_READER = 2; public static final int FIELD_INDEX = 3; } public static final String METHOD_NAME_TO_JSON = "toJson$"; public static final String METHOD_TYPE_TO_JSON = "(Lcom/google/gson/Gson;Lcom/google/gson/stream/JsonWriter;" + ClassUtil.internalTypeFromClassName(NAME_OPTIMIZED_JSON_WRITER) + ")V"; public static final String METHOD_NAME_TO_JSON_BODY = "toJsonBody$"; public static final String METHOD_TYPE_TO_JSON_BODY = "(Lcom/google/gson/Gson;Lcom/google/gson/stream/JsonWriter;" + ClassUtil.internalTypeFromClassName(NAME_OPTIMIZED_JSON_WRITER) + ")V"; public static final class ToJsonLocals { public static final int THIS = 0; public static final int GSON = 1; public static final int JSON_WRITER = 2; public static final int OPTIMIZED_JSON_WRITER = 3; } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/OptimizedJsonFieldCollector.java ================================================ /* * 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.annotation.*; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.*; import java.util.*; /** * This class and member visitor collects the classes and fields that can be * involved in Json (de)serialization and register their Java to Json field * name mapping in an OptimizedJsonInfo object * * @author Lars Vandenbergh * @author Rob Coekaerts */ public class OptimizedJsonFieldCollector implements ClassVisitor, MemberVisitor { private final OptimizedJsonInfo optimizedJsonInfo; private final Mode mode; private OptimizedJsonInfo.ClassJsonInfo classJsonInfo; /** * Creates a new OptimizedJsonFieldCollector. * * @param optimizedJsonInfo contains information on which classes and fields * need to optimized and how. * @param mode whether serialization or deserialization is * being done. */ public OptimizedJsonFieldCollector(OptimizedJsonInfo optimizedJsonInfo, Mode mode) { this.optimizedJsonInfo = optimizedJsonInfo; this.mode = mode; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { classJsonInfo = new OptimizedJsonInfo.ClassJsonInfo(); optimizedJsonInfo.classJsonInfos.put(programClass.getName(), classJsonInfo); optimizedJsonInfo.classIndices.put(programClass.getName(), null); } @Override public void visitLibraryClass(LibraryClass libraryClass) {} // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) {} @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { OptimizedJsonInfo.ClassJsonInfo classJsonInfo = optimizedJsonInfo.classJsonInfos.get(programClass.getName()); programField.attributesAccept(programClass, new AllAnnotationVisitor( new MultiAnnotationVisitor( new AnnotationTypeFilter(GsonClassConstants.ANNOTATION_TYPE_SERIALIZED_NAME, new SerializedNamesCollector(classJsonInfo)), new AnnotationTypeFilter(GsonClassConstants.ANNOTATION_TYPE_EXPOSE, new ExposedFieldsCollector(classJsonInfo, mode))))); String fieldName = programField.getName(programClass); if (classJsonInfo.javaToJsonFieldNames.get(fieldName) == null) { classJsonInfo.javaToJsonFieldNames.put(fieldName, new String[] { fieldName }); optimizedJsonInfo.jsonFieldIndices.put(fieldName, null); } else { for (String jsonFieldName: classJsonInfo.javaToJsonFieldNames.get(fieldName)) { optimizedJsonInfo.jsonFieldIndices.put(jsonFieldName, null); } } } private static class ExposedFieldsCollector implements AnnotationVisitor, ElementValueVisitor, ConstantVisitor { private final OptimizedJsonInfo.ClassJsonInfo classJsonInfo; private final Mode mode; public boolean exposeCurrentField; public ExposedFieldsCollector(OptimizedJsonInfo.ClassJsonInfo classJsonInfo, Mode mode) { this.classJsonInfo = classJsonInfo; this.mode = mode; } // Implementations for AnnotationVisitor @Override public void visitAnnotation(Clazz clazz, Annotation annotation) {} @Override public void visitAnnotation(Clazz clazz, Field field, Annotation annotation) { exposeCurrentField = true; annotation.elementValuesAccept(clazz, this); if (exposeCurrentField) { classJsonInfo.exposedJavaFieldNames.add(field.getName(clazz)); } } // Implementations for ElementValueVisitor @Override public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue) {} @Override public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) { if(constantElementValue.getMethodName(clazz).equals(mode.toString())) { clazz.constantPoolEntryAccept(constantElementValue.u2constantValueIndex, this); } } @Override public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) {} // Implementations for ConstantVisitor @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} @Override public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant) { if (integerConstant.u4value == 0) { exposeCurrentField = false; } } } private static class SerializedNamesCollector implements AnnotationVisitor, ElementValueVisitor { private final OptimizedJsonInfo.ClassJsonInfo classJsonInfo; private List currentJsonFieldNames; public SerializedNamesCollector(OptimizedJsonInfo.ClassJsonInfo classJsonInfo) { this.classJsonInfo = classJsonInfo; } // Implementations for AnnotationVisitor @Override public void visitAnnotation(Clazz clazz, Annotation annotation) {} @Override public void visitAnnotation(Clazz clazz, Field field, Annotation annotation) { currentJsonFieldNames = new ArrayList(); annotation.elementValuesAccept(clazz, this); String[] jsonNamesArray = currentJsonFieldNames.toArray(new String[0]); classJsonInfo.javaToJsonFieldNames.put(field.getName(clazz), jsonNamesArray); } // Implementations for ElementValueVisitor @Override public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue) {} @Override public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) { currentJsonFieldNames.add(clazz.getString(constantElementValue.u2constantValueIndex)); } @Override public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) { arrayElementValue.elementValuesAccept(clazz, annotation, this); } } public enum Mode { serialize, deserialize } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/OptimizedJsonFieldVisitor.java ================================================ /* * 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.util.*; import proguard.classfile.visitor.*; /** * This visitor searches the classes that it visits for fields that can be * involved in Json (de)serialization and passes them on to the given member * visitor. * * For convenience, the classes that are visited are also passed on to the * given class visitor. * * @author Lars Vandenbergh */ public class OptimizedJsonFieldVisitor implements ClassVisitor, MemberVisitor { private final ClassVisitor classVisitor; private final MemberVisitor memberVisitor; /** * Creates a new OptimizedJsonFieldVisitor. * * @param classVisitor the visitor to which (de)serialized classes are * delegated to. * @param memberVisitor the visitor to which (de)serialized fields * are delegated to. */ public OptimizedJsonFieldVisitor(ClassVisitor classVisitor, MemberVisitor memberVisitor) { this.classVisitor = classVisitor; this.memberVisitor = memberVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) {} @Override public void visitProgramClass(ProgramClass programClass) { programClass.accept(classVisitor); programClass.accept(new ClassAccessFilter(0, AccessConstants.ENUM, new AllFieldVisitor( new MemberAccessFilter(0, AccessConstants.TRANSIENT, this)))); // For enums, only visit the enum constant fields. programClass.accept(new ClassAccessFilter(AccessConstants.ENUM, 0, new AllFieldVisitor( new MemberAccessFilter(0, AccessConstants.TRANSIENT, new MemberDescriptorFilter(ClassUtil.internalTypeFromClassName(programClass.getName()), this))))); } // Implementations for MemberVisitor. @Override public void visitAnyMember(Clazz clazz, Member member) {} @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { programField.accept(programClass, memberVisitor); } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/OptimizedJsonInfo.java ================================================ /* * 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 java.util.*; /** * This class keeps track of which Java classes and fields can be involved in * Json (de)serialization and stores their corresponding Json field names * and internal indices. * * @author Lars Vandenbergh * @author Rob Coekaerts */ public class OptimizedJsonInfo { /** * Maps the class name to a unique, contiguous index. * This index is used to generate unique suffixes for the generated toJson * and fromJson methods. */ public Map classIndices = new HashMap(); /** * Maps the json field name to a unique, contiguous index. * This index is used to map Json field names to indices and backwards * in the _OptimizedJsonReader and _OptimizedJsonWriter. */ public Map jsonFieldIndices = new HashMap(); /** * Maps the class name to a ClassJsonInfo. The ClassJsonInfo contains the * names of all the exposed Java fields and their corresponding Json field * name(s). */ public Map classJsonInfos = new HashMap(); /** * Assigns indices to all registered classes and fields. * The generated indices will be contiguous and starting from 0, both * for classes and fields. */ public void assignIndices() { assignIndices(classIndices); assignIndices(jsonFieldIndices); } private void assignIndices(Map indexMap) { int index = 0; for (String fieldName : indexMap.keySet()) { indexMap.put(fieldName, index++); } } public static class ClassJsonInfo { /** * Maps the Java field name to all of its corresponding Json field names. * The first name in the array is the primary name that is used for * writing to Json. The remaining names are alternatives that are * also accepted when reading from Json. */ public Map javaToJsonFieldNames = new HashMap(); /** * Contains the names of all (and only those) Java fields that are * exposed. */ public Set exposedJavaFieldNames = new HashSet(); } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/OptimizedJsonReaderImplInitializer.java ================================================ /* * 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.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.*; import java.util.Map; /** * This code attribute visitor implements the static initializer of * _OptimizedJsonReaderImpl so that the data structure is initialized * with the correct mapping between Json field names and internal * indices. * * @author Lars Vandenbergh */ public class OptimizedJsonReaderImplInitializer implements AttributeVisitor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final CodeAttributeEditor codeAttributeEditor; private final OptimizedJsonInfo deserializationInfo; /** * Creates a new OptimizedJsonReaderImplInitializer. * * @param programClassPool the program class pool used for looking up * program class references. * @param libraryClassPool the library class pool used for looking up * library class references. * @param codeAttributeEditor the code attribute editor used for editing * the code attribute of the static initializer. * @param deserializationInfo contains information on which classes and * fields to deserialize and how. */ public OptimizedJsonReaderImplInitializer(ClassPool programClassPool, ClassPool libraryClassPool, CodeAttributeEditor codeAttributeEditor, OptimizedJsonInfo deserializationInfo) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.codeAttributeEditor = codeAttributeEditor; this.deserializationInfo = deserializationInfo; } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttributeEditor.reset(codeAttribute.u4codeLength); InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz, programClassPool, libraryClassPool); ____.new_(ClassConstants.NAME_JAVA_UTIL_HASH_MAP, libraryClassPool.getClass(ClassConstants.NAME_JAVA_UTIL_HASH_MAP)) .dup() .invokespecial(ClassConstants.NAME_JAVA_UTIL_HASH_MAP, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT); for (Map.Entry jsonFieldIndicesEntry : deserializationInfo.jsonFieldIndices.entrySet()) { ____.dup() .ldc(jsonFieldIndicesEntry.getKey()) .ldc(jsonFieldIndicesEntry.getValue().intValue()) .invokestatic(ClassConstants.NAME_JAVA_LANG_INTEGER, ClassConstants.METHOD_NAME_VALUE_OF, ClassConstants.METHOD_TYPE_VALUE_OF_INT) .invokevirtual(ClassConstants.NAME_JAVA_UTIL_HASH_MAP, ClassConstants.METHOD_NAME_MAP_PUT, ClassConstants.METHOD_TYPE_MAP_PUT) .pop(); } // We replace the instruction that loads null on the stack with the // initialization code and leave the return instruction that comes // right after it in place. codeAttributeEditor.replaceInstruction(0, ____.instructions()); codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/OptimizedJsonWriterImplInitializer.java ================================================ /* * 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.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.*; import java.util.Map; /** * This code attribute visitor implements the static initializer of * _OptimizedJsonWriterImpl so that the data structure is initialized * with the correct mapping between internal indices and Json field names. * * @author Lars Vandenbergh */ public class OptimizedJsonWriterImplInitializer implements AttributeVisitor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final CodeAttributeEditor codeAttributeEditor; private final OptimizedJsonInfo serializationInfo; /** * Creates a new OptimizedJsonWriterImplInitializer. * * @param programClassPool the program class pool used for looking up * program class references. * @param libraryClassPool the library class pool used for looking up * library class references. * @param codeAttributeEditor the code attribute editor used for editing * the code attribute of the static initializer. * @param serializationInfo contains information on which classes and * fields to serialize and how. */ public OptimizedJsonWriterImplInitializer(ClassPool programClassPool, ClassPool libraryClassPool, CodeAttributeEditor codeAttributeEditor, OptimizedJsonInfo serializationInfo) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.codeAttributeEditor = codeAttributeEditor; this.serializationInfo = serializationInfo; } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttributeEditor.reset(codeAttribute.u4codeLength); InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz, programClassPool, libraryClassPool); Map fieldIndices = serializationInfo.jsonFieldIndices; ____.ldc(fieldIndices.size()) .anewarray(ClassConstants.NAME_JAVA_LANG_STRING, libraryClassPool.getClass(ClassConstants.NAME_JAVA_LANG_STRING)); for (Map.Entry fieldIndexEntry : fieldIndices.entrySet()) { ____.dup() .ldc(fieldIndexEntry.getValue().intValue()) .ldc(fieldIndexEntry.getKey()) .aastore(); } // We replace the instruction that loads null on the stack with the // initialization code and leave the return instruction that comes // right after it in place. codeAttributeEditor.replaceInstruction(0, ____.instructions()); codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/OptimizedTypeAdapterAdder.java ================================================ /* * 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.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.JavaTypeConstants; import proguard.classfile.ProgramClass; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassSubHierarchyInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.ClassPoolFiller; import proguard.classfile.visitor.ClassPresenceFilter; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.MultiClassVisitor; import proguard.io.ClassPathDataEntry; import proguard.io.ClassReader; import proguard.io.ExtraDataEntryNameMap; import proguard.util.ProcessingFlagSetter; import proguard.util.ProcessingFlags; import java.io.IOException; import java.util.Map; import static proguard.optimize.gson.OptimizedClassConstants.NAME_OPTIMIZED_TYPE_ADAPTER_IMPL; /** * This ClassVisitor visits domain classes that can be involved in a GSON * (de)serialization and injects an optimized TypeAdapter for each of them. * * @author Lars Vandenbergh */ public class OptimizedTypeAdapterAdder implements ClassVisitor { private static final Logger logger = LogManager.getLogger(OptimizedTypeAdapterAdder.class); //* public static final boolean DEBUG = false; /*/ public static boolean DEBUG = System.getProperty("otaa") != null; //*/ private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final CodeAttributeEditor codeAttributeEditor; private final OptimizedJsonInfo serializationInfo; private final OptimizedJsonInfo deserializationInfo; private final ExtraDataEntryNameMap extraDataEntryNameMap; private final Map typeAdapterRegistry; private final GsonRuntimeSettings gsonRuntimeSettings; /** * Creates a new OptimizedTypeAdapterAdder. * * @param programClassPool the program class pool used for looking * up references to program classes. * @param libraryClassPool the library class pool used for looking * up references to library classes. * @param codeAttributeEditor the code attribute editor used for * implementing the added type adapters. * @param serializationInfo contains information on which classes * and fields to serialize and how. * @param deserializationInfo contains information on which classes * and fields to deserialize and how. * @param extraDataEntryNameMap map to which the names of new type * adapter classes are added. * @param typeAdapterRegistry the registry to which the corresponding * type adapter class name is added for a * given domain class name. * @param gsonRuntimeSettings keeps track of all GsonBuilder invocations. */ public OptimizedTypeAdapterAdder(ClassPool programClassPool, ClassPool libraryClassPool, CodeAttributeEditor codeAttributeEditor, OptimizedJsonInfo serializationInfo, OptimizedJsonInfo deserializationInfo, ExtraDataEntryNameMap extraDataEntryNameMap, Map typeAdapterRegistry, GsonRuntimeSettings gsonRuntimeSettings) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.codeAttributeEditor = codeAttributeEditor; this.serializationInfo = serializationInfo; this.deserializationInfo = deserializationInfo; this.extraDataEntryNameMap = extraDataEntryNameMap; this.typeAdapterRegistry = typeAdapterRegistry; this.gsonRuntimeSettings = gsonRuntimeSettings; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Derive class name for optimized type adapter from the name of the // domain class. String externalClassName = ClassUtil.externalClassName(programClass.getName()); String packageName = ClassUtil.externalPackageName(externalClassName); String shortClassName = ClassUtil.externalShortClassName(externalClassName); String externalTypeAdapterClassName = packageName + JavaTypeConstants.PACKAGE_SEPARATOR + "Optimized" + shortClassName + "TypeAdapter"; String typeAdapterClassName = ClassUtil.internalClassName(externalTypeAdapterClassName); if (programClassPool.getClass(typeAdapterClassName) == null) { logger.debug("OptimizedTypeAdapterAdder: injecting {}", typeAdapterClassName); ClassReader templateClassReader = new ClassReader(false, false, false, false, null, new OptimizedTypeAdapterInitializer( typeAdapterClassName, programClass, codeAttributeEditor, serializationInfo, deserializationInfo, gsonRuntimeSettings.instanceCreatorClassPool, new MultiClassVisitor( new ProcessingFlagSetter(ProcessingFlags.INJECTED), new ClassPresenceFilter(programClassPool, null, new ClassPoolFiller(programClassPool)), new ClassReferenceInitializer(programClassPool, libraryClassPool), new ClassSubHierarchyInitializer()))); try { String dataEntryName = getDataEntryName(NAME_OPTIMIZED_TYPE_ADAPTER_IMPL); templateClassReader.read(new ClassPathDataEntry(dataEntryName)); extraDataEntryNameMap.addExtraClassToClass(programClass.getName(), typeAdapterClassName); typeAdapterRegistry.put(programClass.getName(), typeAdapterClassName); } catch (IOException e) { throw new RuntimeException(e); } } } // Utility methods. private static String getDataEntryName(String internalClassName) { // This is mostly done to make sure the internal class name gets // adapted properly during obfuscation. return internalClassName + ".class"; } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/OptimizedTypeAdapterFactoryInitializer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.CodeAttribute; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import java.lang.reflect.Modifier; import java.util.Map; /** * This visitor implements the getType() method of the injected * _OptimizedTypeAdapterFactory. * * @author Lars Vandenbergh */ public class OptimizedTypeAdapterFactoryInitializer implements InstructionVisitor { private final ClassPool programClassPool; private final CodeAttributeEditor codeAttributeEditor; private final Map typeAdapterRegistry; private final GsonRuntimeSettings gsonRuntimeSettings; /** * Creates a new OptimizedTypeAdapterFactoryInitializer. * * @param programClassPool the program class pool used for looking up * references to program classes. * @param codeAttributeEditor the code attribute editor used for editing * the code attribute of the getType() method. * @param typeAdapterRegistry contains the mapping between domain class * names and their corresponding type adapter * class name. * @param gsonRuntimeSettings keeps track of all GsonBuilder invocations. */ public OptimizedTypeAdapterFactoryInitializer(ClassPool programClassPool, CodeAttributeEditor codeAttributeEditor, Map typeAdapterRegistry, GsonRuntimeSettings gsonRuntimeSettings) { this.programClassPool = programClassPool; this.codeAttributeEditor = codeAttributeEditor; this.typeAdapterRegistry = typeAdapterRegistry; this.gsonRuntimeSettings = gsonRuntimeSettings; } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { if (method.getName(clazz).equals(OptimizedClassConstants.METHOD_NAME_CREATE) && method.getDescriptor(clazz).equals(OptimizedClassConstants.METHOD_TYPE_CREATE) && instruction.actualOpcode() == Instruction.OP_ACONST_NULL) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz); CodeAttributeEditor.Label end = codeAttributeEditor.label(); // Don't use optimized type adapter when an unsupported property is // enabled during the construction of the Gson object. if (gsonRuntimeSettings.setFieldNamingPolicy || gsonRuntimeSettings.setFieldNamingStrategy) { ____.aload_1() // gson argument .getfield(GsonClassConstants.NAME_GSON, GsonClassConstants.FIELD_NAME_FIELD_NAMING_STRATEGY, GsonClassConstants.FIELD_TYPE_FIELD_NAMING_STRATEGY) .getstatic(GsonClassConstants.NAME_FIELD_NAMING_POLICY, GsonClassConstants.FIELD_NAME_IDENTITY, GsonClassConstants.FIELD_TYPE_IDENTITY) .ifacmpne(end.offset()); } if (gsonRuntimeSettings.excludeFieldsWithModifiers) { ____.aload_1() // gson argument .getfield(GsonClassConstants.NAME_GSON, GsonClassConstants.FIELD_NAME_EXCLUDER, GsonClassConstants.FIELD_TYPE_EXCLUDER) .getfield(GsonClassConstants.NAME_EXCLUDER, GsonClassConstants.FIELD_NAME_MODIFIERS, GsonClassConstants.FIELD_TYPE_MODIFIERS) .ldc(Modifier.STATIC | Modifier.TRANSIENT) .ificmpne(end.offset()); } if (gsonRuntimeSettings.setExclusionStrategies || gsonRuntimeSettings.addSerializationExclusionStrategy || gsonRuntimeSettings.addDeserializationExclusionStrategy) { ____.aload_1() // gson argument .getfield(GsonClassConstants.NAME_GSON, GsonClassConstants.FIELD_NAME_EXCLUDER, GsonClassConstants.FIELD_TYPE_EXCLUDER) .getfield(GsonClassConstants.NAME_EXCLUDER, GsonClassConstants.FIELD_NAME_SERIALIZATION_STRATEGIES, GsonClassConstants.FIELD_TYPE_SERIALIZATION_STRATEGIES) .invokevirtual(ClassConstants.NAME_JAVA_UTIL_LIST, ClassConstants.METHOD_NAME_IS_EMPTY, ClassConstants.METHOD_TYPE_IS_EMPTY) .ifeq(end.offset()); ____.aload_1() // gson argument .getfield(GsonClassConstants.NAME_GSON, GsonClassConstants.FIELD_NAME_EXCLUDER, GsonClassConstants.FIELD_TYPE_EXCLUDER) .getfield(GsonClassConstants.NAME_EXCLUDER, GsonClassConstants.FIELD_NAME_DESERIALIZATION_STRATEGIES, GsonClassConstants.FIELD_TYPE_DESERIALIZATION_STRATEGIES) .invokevirtual(ClassConstants.NAME_JAVA_UTIL_LIST, ClassConstants.METHOD_NAME_IS_EMPTY, ClassConstants.METHOD_TYPE_IS_EMPTY) .ifeq(end.offset()); } if (gsonRuntimeSettings.setVersion) { ____.aload_1() // gson argument .getfield(GsonClassConstants.NAME_GSON, GsonClassConstants.FIELD_NAME_EXCLUDER, GsonClassConstants.FIELD_TYPE_EXCLUDER) .getfield(GsonClassConstants.NAME_EXCLUDER, GsonClassConstants.FIELD_NAME_VERSION, GsonClassConstants.FIELD_TYPE_VERSION) .ldc2_w(-1d) .dcmpg() .ifne(end.offset()); } for (Map.Entry typeAdapterRegistryEntry : typeAdapterRegistry.entrySet()) { String objectType = typeAdapterRegistryEntry.getKey(); String adapterType = typeAdapterRegistryEntry.getValue(); Clazz objectClazz = programClassPool.getClass(objectType); CodeAttributeEditor.Label elseif = codeAttributeEditor.label(); if ((objectClazz.getAccessFlags() & AccessConstants.ENUM) == 0) { // For non-enum classes, we do the following check to // decide whether this is TypeAdapter to use: // typeToken.getRawType() == objectClazz.class ____.aload_2() // type argument .invokevirtual(GsonClassConstants.NAME_TYPE_TOKEN, GsonClassConstants.METHOD_NAME_GET_RAW_TYPE, GsonClassConstants.METHOD_TYPE_GET_RAW_TYPE) .ldc(objectClazz) .ifacmpne(elseif.offset()); } else { // For enum classes, we do the following check to decide // whether this is the good TypeAdapter to use, since we // don't optimize the subclasses of the enum that are // generated by the compiler: // objectClazz.class.isAssignableFrom(typeToken.getRawType()) ____.ldc(objectClazz) .aload_2() // type argument .invokevirtual(GsonClassConstants.NAME_TYPE_TOKEN, GsonClassConstants.METHOD_NAME_GET_RAW_TYPE, GsonClassConstants.METHOD_TYPE_GET_RAW_TYPE) .invokevirtual(ClassConstants.NAME_JAVA_LANG_CLASS, ClassConstants.METHOD_NAME_CLASS_IS_ASSIGNABLE_FROM, ClassConstants.METHOD_TYPE_CLASS_IS_ASSIGNABLE_FROM) .ifeq(elseif.offset()); } // Instantiate type adapter and return it. ____.new_(adapterType) .dup() .aload_1() // gson argument .getstatic(clazz.getName(), OptimizedClassConstants.FIELD_NAME_OPTIMIZED_JSON_READER_IMPL, OptimizedClassConstants.FIELD_TYPE_OPTIMIZED_JSON_READER_IMPL) .getstatic(clazz.getName(), OptimizedClassConstants.FIELD_NAME_OPTIMIZED_JSON_WRITER_IMPL, OptimizedClassConstants.FIELD_TYPE_OPTIMIZED_JSON_WRITER_IMPL) .invokespecial(adapterType, ClassConstants.METHOD_NAME_INIT, OptimizedClassConstants.METHOD_TYPE_INIT) .areturn() .label(elseif); } ____.label(end) .aconst_null(); codeAttributeEditor.replaceInstruction(offset,____.instructions()); } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/OptimizedTypeAdapterInitializer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.editor.CodeAttributeEditor.Label; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import java.util.*; /** * This class visitor transforms the template _OptimizedTypeAdapter into a full * implementation of a GSON TypeAdapter for a specific domain class. * * The read() and write() methods will be implemented appropriately and will * delegate to the toJson$xxx() and fromJson$xxx() methods that are injected * into the domain classes. * * The visited class will be renamed, e.g. if the domain class for which the * TypeAdapter is meant to be used is called DomainObject, the class name will * be OptimizedDomainObjectTypeAdapter. * * @author Lars Vandenbergh */ public class OptimizedTypeAdapterInitializer implements ClassVisitor { private final String typeAdapterClassName; private final String objectClassName; private final ProgramClass objectProgramClass; private final CodeAttributeEditor codeAttributeEditor; private final OptimizedJsonInfo serializationIndexMap; private final OptimizedJsonInfo deserializationIndexMap; private final ClassPool instanceCreatorClasses; private final ClassVisitor classVisitor; /** * Creates a new OptimizedTypeAdapterInitializer. * * @param typeAdapterClassName the class name of the optimized type * adapter. * @param objectProgramClass the class name of the domain class for * which the type adapter is meant. * @param codeAttributeEditor the code attribute editor used to edit * the code attribute of the read() and * write() methods. * @param serializationInfo contains information on which classes * and fields to serialize and how. * @param deserializationInfo contains information on which classes * and fields to deserialize and how. * @param instanceCreatorClasses class pool that contains all domain * classes for which an InstanceCreator * is registered. * @param classVisitor visitor to which all implemented type * adapters are delegated. */ public OptimizedTypeAdapterInitializer(String typeAdapterClassName, ProgramClass objectProgramClass, CodeAttributeEditor codeAttributeEditor, OptimizedJsonInfo serializationInfo, OptimizedJsonInfo deserializationInfo, ClassPool instanceCreatorClasses, ClassVisitor classVisitor) { this.typeAdapterClassName = typeAdapterClassName; this.objectClassName = ClassUtil.internalClassName(objectProgramClass.getName()); this.objectProgramClass = objectProgramClass; this.codeAttributeEditor = codeAttributeEditor; this.serializationIndexMap = serializationInfo; this.deserializationIndexMap = deserializationInfo; this.instanceCreatorClasses = instanceCreatorClasses; this.classVisitor = classVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) {} @Override public void visitProgramClass(ProgramClass programClass) { // Rename template class to specific type adapter class name. programClass.thisClassConstantAccept(new TypeAdapterRenamer()); programClass.methodsAccept( new AllAttributeVisitor( new AllAttributeVisitor( new LocalVariableTypeRenamer()))); boolean isEnumAdapter = (objectProgramClass.getAccessFlags() & AccessConstants.ENUM) != 0; if (isEnumAdapter) { // Make sure the enum is accessible from the type adapter. objectProgramClass.u2accessFlags &= ~AccessConstants.PRIVATE; objectProgramClass.u2accessFlags |= AccessConstants.PUBLIC; } AttributeVisitor readImplementer = isEnumAdapter ? new EnumReadImplementer(): new ReadImplementer(); AttributeVisitor writeImplementer = isEnumAdapter ? new EnumWriteImplementer(): new WriteImplementer(); if (deserializationIndexMap.classIndices.get(objectClassName) != null) { programClass.methodsAccept(new MemberNameFilter(OptimizedClassConstants.METHOD_NAME_READ, new AllAttributeVisitor( readImplementer))); } if (serializationIndexMap.classIndices.get(objectClassName) != null) { programClass.methodsAccept(new MemberNameFilter(OptimizedClassConstants.METHOD_NAME_WRITE, new AllAttributeVisitor( writeImplementer))); } // Pass on to class visitor. classVisitor.visitProgramClass(programClass); } private class TypeAdapterRenamer implements ConstantVisitor { // Implementations for ConstantVisitor. @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} @Override public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { classConstant.u2nameIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(typeAdapterClassName); } } private class LocalVariableTypeRenamer implements AttributeVisitor, LocalVariableInfoVisitor { // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { localVariableTableAttribute.localVariablesAccept(clazz,method,codeAttribute,this); } // Implementations for LocalVariableInfoVisitor. @Override public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) { String descriptor = localVariableInfo.getDescriptor(clazz); if (descriptor.equals(OptimizedClassConstants.TYPE_OPTIMIZED_TYPE_ADAPTER_IMPL)) { localVariableInfo.u2descriptorIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(ClassUtil.internalTypeFromClassName(typeAdapterClassName)); } } } /** * Visits the code attribute of the the read() method of the TypeAdapter and * provides it with a proper implementation for enum types. */ private class EnumReadImplementer implements AttributeVisitor { // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttributeEditor.reset(codeAttribute.u4codeLength); InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz); // Deserialization Map javaToJsonValueNames = deserializationIndexMap.classJsonInfos.get(objectClassName).javaToJsonFieldNames; Map jsonFieldIndices = deserializationIndexMap.jsonFieldIndices; List switchCases = new ArrayList(); for (String javaValueName : javaToJsonValueNames.keySet()) { for (String jsonValueName : javaToJsonValueNames.get(javaValueName)) { switchCases.add(new SwitchCase(javaValueName, codeAttributeEditor.label(), jsonFieldIndices.get(jsonValueName))); } } Collections.sort(switchCases); int[] cases = new int[switchCases.size()]; int[] jumpOffsets = new int[switchCases.size()]; for (int index = 0; index < switchCases.size(); index++) { cases[index] = switchCases.get(index).stringIndex; jumpOffsets[index] = switchCases.get(index).label.offset(); } CodeAttributeEditor.Label defaultCase = codeAttributeEditor.label(); ____.aload(OptimizedClassConstants.ReadLocals.THIS) .getfield(typeAdapterClassName, OptimizedClassConstants.FIELD_NAME_OPTIMIZED_JSON_READER, OptimizedClassConstants.FIELD_TYPE_OPTIMIZED_JSON_READER) .aload(OptimizedClassConstants.ReadLocals.JSON_READER) .invokevirtual(OptimizedClassConstants.NAME_OPTIMIZED_JSON_READER, OptimizedClassConstants.METHOD_NAME_NEXT_VALUE_INDEX, OptimizedClassConstants.METHOD_TYPE_NEXT_VALUE_INDEX); ____.lookupswitch(defaultCase.offset(), cases, jumpOffsets); for (int index = 0; index < switchCases.size(); index++) { ____.label(switchCases.get(index).label) .getstatic(objectClassName, switchCases.get(index).enumConstant, ClassUtil.internalTypeFromClassName(objectClassName)) .areturn(); } ____.label(defaultCase) .aconst_null() .areturn(); codeAttributeEditor.replaceInstruction(0, ____.instructions()); codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } } private static class SwitchCase implements Comparable { private String enumConstant; private Label label; private int stringIndex; public SwitchCase(String enumConstant, Label label, int stringIndex) { this.enumConstant = enumConstant; this.label = label; this.stringIndex = stringIndex; } @Override public int compareTo(SwitchCase switchCase) { return this.stringIndex - switchCase.stringIndex; } } /** * Visits the code attribute of the the read() method of the TypeAdapter and * provides it with a proper implementation for non-enum types. */ private class ReadImplementer implements AttributeVisitor { // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttributeEditor.reset(codeAttribute.u4codeLength); InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz); Integer classIndex = deserializationIndexMap.classIndices.get(objectClassName); String methodNameFromJson = OptimizedClassConstants.METHOD_NAME_FROM_JSON + classIndex; CodeAttributeEditor.Label nonNull = codeAttributeEditor.label(); // Peek the next value and check if it is null. ____.aload(OptimizedClassConstants.ReadLocals.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) .ifacmpne(nonNull.offset()); // If it is null, skip value in JSON. ____.aload(OptimizedClassConstants.ReadLocals.JSON_READER) .invokevirtual(GsonClassConstants.NAME_JSON_READER, GsonClassConstants.METHOD_NAME_SKIP_VALUE, GsonClassConstants.METHOD_TYPE_SKIP_VALUE); // Return null as result. ____.aconst_null() .areturn(); // If the next value is not null, create an instance of the domain class. ____.label(nonNull); if (instanceCreatorClasses.getClass(objectClassName) == null) { ____.new_(objectClassName) .dup() .invokespecial(objectClassName, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT) .astore(OptimizedClassConstants.ReadLocals.VALUE); } else { // For classes for which an InstanceCreator is registered, we // let the instance creator instantiate the class. ____.aload(OptimizedClassConstants.ReadLocals.THIS) .getfield(typeAdapterClassName, OptimizedClassConstants.FIELD_NAME_GSON, OptimizedClassConstants.FIELD_TYPE_GSON) .getfield(GsonClassConstants.NAME_GSON, GsonClassConstants.FIELD_NAME_INSTANCE_CREATORS, GsonClassConstants.FIELD_TYPE_INSTANCE_CREATORS) .ldc(instanceCreatorClasses.getClass(objectClassName)) .invokevirtual(ClassConstants.NAME_JAVA_UTIL_MAP, ClassConstants.METHOD_NAME_MAP_GET, ClassConstants.METHOD_TYPE_MAP_GET) .checkcast(GsonClassConstants.NAME_INSTANCE_CREATOR) .ldc(instanceCreatorClasses.getClass(objectClassName)) .invokevirtual(GsonClassConstants.NAME_INSTANCE_CREATOR, GsonClassConstants.METHOD_NAME_CREATE_INSTANCE, GsonClassConstants.METHOD_TYPE_CREATE_INSTANCE) .checkcast(objectClassName) .astore(OptimizedClassConstants.ReadLocals.VALUE); } // Deserialize object by calling its fromJson$ method. ____.aload(OptimizedClassConstants.ReadLocals.VALUE) .aload(OptimizedClassConstants.ReadLocals.THIS) .getfield(typeAdapterClassName, OptimizedClassConstants.FIELD_NAME_GSON, OptimizedClassConstants.FIELD_TYPE_GSON) .aload(OptimizedClassConstants.ReadLocals.JSON_READER) .aload(OptimizedClassConstants.ReadLocals.THIS) .getfield(typeAdapterClassName, OptimizedClassConstants.FIELD_NAME_OPTIMIZED_JSON_READER, OptimizedClassConstants.FIELD_TYPE_OPTIMIZED_JSON_READER) .invokevirtual(objectClassName, methodNameFromJson, OptimizedClassConstants.METHOD_TYPE_FROM_JSON); // Return deserialized object. ____.aload(OptimizedClassConstants.ReadLocals.VALUE) .areturn(); codeAttributeEditor.replaceInstruction(0, ____.instructions()); codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } } /** * Visits the code attribute of the the write() method of the TypeAdapter and * provides it with a proper implementation for enum types. */ private class EnumWriteImplementer implements AttributeVisitor { // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttributeEditor.reset(codeAttribute.u4codeLength); InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz); Label nonNull = codeAttributeEditor.label(); Label end = codeAttributeEditor.label(); Label writeValue = codeAttributeEditor.label(); // Check if the passed value is null. ____.aload(OptimizedClassConstants.WriteLocals.VALUE) .ifnonnull(nonNull.offset()); // If it is null, write null value in JSON. ____.aload(OptimizedClassConstants.WriteLocals.JSON_WRITER) .invokevirtual(GsonClassConstants.NAME_JSON_WRITER, GsonClassConstants.METHOD_NAME_NULL_VALUE, GsonClassConstants.METHOD_TYPE_NULL_VALUE) .pop() .goto_(end.offset()); // If it is not null, serialize value. Map javaToJsonValueNames = serializationIndexMap.classJsonInfos.get(objectClassName).javaToJsonFieldNames; Map jsonFieldIndices = serializationIndexMap.jsonFieldIndices; ____.label(nonNull) .aload(OptimizedClassConstants.WriteLocals.THIS) .getfield(typeAdapterClassName, OptimizedClassConstants.FIELD_NAME_OPTIMIZED_JSON_WRITER, OptimizedClassConstants.FIELD_TYPE_OPTIMIZED_JSON_WRITER) .aload(OptimizedClassConstants.WriteLocals.JSON_WRITER) .aload(OptimizedClassConstants.WriteLocals.VALUE); for (String javaValueName: javaToJsonValueNames.keySet()) { Label label = codeAttributeEditor.label(); String jsonValueName = javaToJsonValueNames.get(javaValueName)[0]; ____.dup() .getstatic(objectClassName, javaValueName, ClassUtil.internalTypeFromClassName(objectClassName)) .ifacmpne(label.offset()) .pop() .ldc(jsonFieldIndices.get(jsonValueName).intValue()) .goto_(writeValue.offset()) .label(label); } ____.pop() .iconst_m1(); ____.label(writeValue) .invokevirtual(OptimizedClassConstants.NAME_OPTIMIZED_JSON_WRITER, OptimizedClassConstants.METHOD_NAME_VALUE, OptimizedClassConstants.METHOD_TYPE_VALUE) .label(end) .return_(); codeAttributeEditor.replaceInstruction(0, ____.instructions()); codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } } /** * Visits the code attribute of the the write() method of the TypeAdapter and * provides it with a proper implementation for non-enum types. */ private class WriteImplementer implements AttributeVisitor { // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttributeEditor.reset(codeAttribute.u4codeLength); InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz); Integer classIndex = serializationIndexMap.classIndices.get(objectClassName); String methodNameToJson = OptimizedClassConstants.METHOD_NAME_TO_JSON + classIndex; CodeAttributeEditor.Label nonNull = codeAttributeEditor.label(); CodeAttributeEditor.Label end = codeAttributeEditor.label(); // Check if the passed value is null. ____.aload(OptimizedClassConstants.WriteLocals.VALUE) .ifnonnull(nonNull.offset()); // If it is null, write null value in JSON. ____.aload(OptimizedClassConstants.WriteLocals.JSON_WRITER) .invokevirtual(GsonClassConstants.NAME_JSON_WRITER, GsonClassConstants.METHOD_NAME_NULL_VALUE, GsonClassConstants.METHOD_TYPE_NULL_VALUE) .pop() .goto_(end.offset()); // If the next value is not null, serialize object by calling its toJson$ method. ____.label(nonNull) .aload(OptimizedClassConstants.WriteLocals.VALUE) .checkcast(objectClassName) .aload(OptimizedClassConstants.WriteLocals.THIS) .getfield(typeAdapterClassName, OptimizedClassConstants.FIELD_NAME_GSON, OptimizedClassConstants.FIELD_TYPE_GSON) .aload(OptimizedClassConstants.WriteLocals.JSON_WRITER) .aload(OptimizedClassConstants.WriteLocals.THIS) .getfield(typeAdapterClassName, OptimizedClassConstants.FIELD_NAME_OPTIMIZED_JSON_WRITER, OptimizedClassConstants.FIELD_TYPE_OPTIMIZED_JSON_WRITER) .invokevirtual(objectClassName, methodNameToJson, OptimizedClassConstants.METHOD_TYPE_TO_JSON) .label(end) .return_(); codeAttributeEditor.replaceInstruction(0, ____.instructions()); codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/TypeArgumentFinder.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.SignatureAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.Constant; import proguard.classfile.constant.MethodrefConstant; import proguard.classfile.constant.RefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.VariableInstruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; import proguard.evaluation.value.InstructionOffsetValue; import proguard.util.ArrayUtil; /** * This instructions visitor is used for 2 purposes. * * 1. This instruction visitor searches for types behind Class and TypeToken * (from the Gson library) arguments. The intent is to let a producing instruction * that put a Type/Class/TypeToken on the stack accept this visitor, which determines * the actual type that is being deserialized. * * Currently two types of situations are supported: * - You pass the type via a .class call (ldc instruction) * example: * fromJson("", MyClass.class) * result: MyClass * - You pass the type from a getType call via a variable (aload instruction) * example: * Type type = new TypeToken() {}.getType(); * fromJson("", type); * result: MyClass * * 2. This instruction visitor returns the type of a TypeAdapter argument. * * - When a TypeAdapter is created (new instruction) * example: * registerTypeHierarchyAdapter(MyClass.class, new MyTypeAdapter()) * result: MyTypeAdapter */ class TypeArgumentFinder implements InstructionVisitor, ConstantVisitor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final PartialEvaluator partialEvaluator; String[] typeArgumentClasses; /** * Creates a new TypeArgumentFinder. * * @param programClassPool the program class pool used for looking up * class references. * @param libraryClassPool the library class pool used for looking up * class references. * @param partialEvaluator the partial evaluator used to evaluate visited * code attributes. */ TypeArgumentFinder(ClassPool programClassPool, ClassPool libraryClassPool, PartialEvaluator partialEvaluator) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.partialEvaluator = partialEvaluator; } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { if (variableInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { // Find the operation that stored the loaded Type. LastStoreFinder lastStoreFinder = new LastStoreFinder(variableInstruction.variableIndex); codeAttribute.instructionsAccept(clazz, method, 0, offset, lastStoreFinder); if (lastStoreFinder.lastStore != null) { // Find out which instruction produced the stored Type. TracedStack stackBeforeStore = partialEvaluator.getStackBefore(lastStoreFinder.lastStoreOffset); InstructionOffsetValue instructionOffsetValue = stackBeforeStore.getTopProducerValue(0).instructionOffsetValue(); // Derive the signature of the subclass of TypeToken from which the Type is retrieved. TypeTokenSignatureFinder typeTokenFinder = new TypeTokenSignatureFinder(); for (int offsetIndex = 0; offsetIndex < instructionOffsetValue.instructionOffsetCount(); offsetIndex++) { int instructionOffset = instructionOffsetValue.instructionOffset(offsetIndex); codeAttribute.instructionAccept(clazz, method, instructionOffset, typeTokenFinder); } // Derive the classes from the signature of the TypeToken subclass. if (typeTokenFinder.typeTokenSignature != null) { typeArgumentClasses = new String[0]; Clazz[] referencedClasses = typeTokenFinder.typeTokenSignature.referencedClasses; for (Clazz referencedClass : referencedClasses) { if (referencedClass!= null && !referencedClass.getName().equals(GsonClassConstants.NAME_TYPE_TOKEN)) { typeArgumentClasses = ArrayUtil.add(typeArgumentClasses, typeArgumentClasses.length, referencedClass.getName()); } } } } } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { // Used in cases where a .class argument is passed. case Instruction.OP_LDC: // Fallthrough case Instruction.OP_LDC2_W: // Fallthrough case Instruction.OP_LDC_W: // Fallthrough // Used in the case where a new TypeAdapter is passed. case Instruction.OP_NEW: clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; default: // These cases are not supported, so we do not update the typeArgumentClasses array. } } // Implementations for ConstantVisitor. @Override public void visitAnyConstant(Clazz clazz, Constant constant) { } @Override public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { typeArgumentClasses = new String[] { refConstant.getClassName(clazz) }; } @Override public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { typeArgumentClasses = new String[] { classConstant.getName(clazz) }; } private static class LastStoreFinder implements InstructionVisitor { private final int variableIndex; private int lastStoreOffset; private VariableInstruction lastStore; public LastStoreFinder(int variableIndex) { this.variableIndex = variableIndex; } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { } @Override public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { if(variableInstruction.variableIndex == variableIndex && variableInstruction.canonicalOpcode() == Instruction.OP_ASTORE){ lastStoreOffset = offset; lastStore = variableInstruction; } } } private class TypeTokenSignatureFinder implements InstructionVisitor, ConstantVisitor, AttributeVisitor { private SignatureAttribute typeTokenSignature; // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { } @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } // Implementations for ConstantVisitor. @Override public void visitAnyConstant(Clazz clazz, Constant constant) { } @Override public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { if (methodrefConstant.referencedClass != null) { if (GsonClassConstants.NAME_TYPE_TOKEN.equals(methodrefConstant.referencedClass.getName()) && GsonClassConstants.METHOD_NAME_GET_TYPE.equals(methodrefConstant.getName(clazz))) { programClassPool.classAccept(methodrefConstant.getClassName(clazz), new AllAttributeVisitor(this)); libraryClassPool.classAccept(methodrefConstant.getClassName(clazz), new AllAttributeVisitor(this)); } } } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) { } @Override public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) { typeTokenSignature = signatureAttribute; } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/TypeParameterClassChecker.java ================================================ /* * 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.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.visitor.*; /** * Checks whether a visited class contains fields that have generic type * parameters. * * For example: * *

 * public class Foo
 * {
 *     private T field;
 * }
 * 
* * @author Lars Vandenbergh */ class TypeParameterClassChecker implements ClassVisitor, AttributeVisitor { public boolean hasFieldWithTypeParameter; // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) {} @Override public void visitProgramClass(ProgramClass programClass) { programClass.fieldsAccept(new AllAttributeVisitor(this)); } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitSignatureAttribute(Clazz clazz, Field field, SignatureAttribute signatureAttribute) { String fieldSignature = signatureAttribute.getSignature(clazz); hasFieldWithTypeParameter = hasFieldWithTypeParameter || fieldSignature.startsWith("T") || fieldSignature.contains(" ____ .aload_0() .invokespecial(GsonClassConstants.NAME_TYPE_TOKEN, ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT) .return_()) .getProgramClass(); subClass.accept(new ProgramClassOptimizationInfoSetter(true)); programClassPool.classAccept(GsonClassConstants.NAME_TYPE_TOKEN, new SubclassAdder(subClass)); // Add signature attribute with full generic type. ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(subClass); int attributeNameIndex = constantPoolEditor.addUtf8Constant("Signature"); String classSignature = "Lcom/google/gson/reflect/TypeToken<" + this.fieldSignature + ">;"; int signatureIndex = constantPoolEditor.addUtf8Constant(classSignature); AttributesEditor attributesEditor = new AttributesEditor(subClass, false); attributesEditor.addAttribute(new SignatureAttribute(attributeNameIndex, signatureIndex)); return subClass; } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/_GsonUtil.java ================================================ /* * 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 com.google.gson.Gson; import com.google.gson.TypeAdapter; import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Map; /** * This utility class is injected into the program class pool when the GSON * optimizations are applied. It contains the logic for picking the right * type adapter for a given type and value that needs to be serialized. * * The injected toJson() methods in the domain classes will use these utility * methods for serializing the fields of the domain class with the appropriate * type adapter. * * @author Lars Vandenbergh */ public final class _GsonUtil { /** * Returns the appropriate type adapter for handling the given value with * the given declared type. * * @param gson the Gson context that manages all registered type * adapters. * @param declaredType the type of the value to (de)serialize. * @param value the value to (de)serialize. * @return the type adapter for handling the given value and * declared type. */ public static TypeAdapter getTypeAdapter(Gson gson, Class declaredType, Object value) { // If the runtime type is a sub type and there is a custom type adapter registered for // the declared type, that one should get precedence over the runtime type adapter if // the runtime type adapter is not custom. Type runtimeType = getRuntimeTypeIfMoreSpecific(declaredType, value); TypeAdapter runtimeTypeAdapter = gson.getAdapter(TypeToken.get(runtimeType)); if (declaredType != runtimeType && !isCustomTypeAdapter(runtimeTypeAdapter)) { TypeAdapter declaredTypeAdapter = gson.getAdapter(declaredType); if (isCustomTypeAdapter(declaredTypeAdapter)) { return declaredTypeAdapter; } } // In all other cases the type adapter for the runtime type is used. return runtimeTypeAdapter; } /** * Returns the appropriate type adapter for handling the given value with * the given declared type token. * * @param gson the Gson context that manages all registered type * adapters. * @param declaredTypeToken the declared type token of the value to (de)serialize. * @param value the value to (de)serialize. * @return the type adapter for handling the given value and * declared type. */ public static TypeAdapter getTypeAdapter(Gson gson, TypeToken declaredTypeToken, Object value) { // If the runtime type is a sub type and there is a custom type adapter registered for // the declared type, that one should get precedence over the runtime type adapter if // the runtime type adapter is not custom. Type declaredType = declaredTypeToken.getType(); Type runtimeType = getRuntimeTypeIfMoreSpecific(declaredType, value); TypeAdapter runtimeTypeAdapter = gson.getAdapter(TypeToken.get(runtimeType)); if (declaredType != runtimeType && !isCustomTypeAdapter(runtimeTypeAdapter)) { TypeAdapter declaredTypeAdapter = gson.getAdapter(declaredTypeToken); if (isCustomTypeAdapter(declaredTypeAdapter)) { return declaredTypeAdapter; } } // In all other cases the type adapter for the runtime type is used. return runtimeTypeAdapter; } /** * Finds a compatible runtime type if it is more specific */ private static Type getRuntimeTypeIfMoreSpecific(Type type, Object value) { if (value != null && (type == Object.class || type instanceof TypeVariable || type instanceof Class)) { type = value.getClass(); } return type; } /** * Determines whether a given type adapter is a custom type adapter, i.e. * a type adapter that is registered by the user of the Gson API and not * the GSON reflection based type adapter or the optimized type adapter * injected. */ private static boolean isCustomTypeAdapter(TypeAdapter declaredTypeAdapter) { return !(declaredTypeAdapter instanceof _OptimizedTypeAdapter) && !(declaredTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter); } /** * Dumps the cached type adapter for each type for debugging purpose. */ public static void dumpTypeTokenCache(String message, Map, TypeAdapter> typeTokenCache) { System.out.println(message); for (Map.Entry, TypeAdapter> typeTokenCacheEntry : typeTokenCache.entrySet()) { System.out.println(" " + typeTokenCacheEntry.getKey() + " -> " + typeTokenCacheEntry.getValue()); } } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/_OptimizedJsonReader.java ================================================ /* * 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 com.google.gson.stream.*; import java.io.IOException; /** * Interface for reading Json fields and values using an internal index. * This allows injecting optimized Java code that reads and interprets Json * without referring to Json field names and values using plain Strings. * * @author Lars Vandenbergh */ public interface _OptimizedJsonReader { /** * Reads the internal index of the next Json field from the given Json * reader. * * The original name of this method is "nextFieldIndex". * * The name of this method has already been obfuscated because it is part * of an injected class. * * When renaming this field, the corresponding constant in * OptimizedClassConstants needs to be updated accordingly. * * @param jsonReader the Json reader to read from. * @return the internal index of the read field value. * @throws IOException if the reading failed. */ int b(JsonReader jsonReader) throws IOException; /** * Reads the internal index of the next Json value from the given Json * reader. * * The original name of this method is "nextValueIndex". * * The name of this method has already been obfuscated because it is part * of an injected class. * * When renaming this method, the corresponding constant in * OptimizedClassConstants needs to be updated accordingly. * * @param jsonReader the Json reader to read from. * @return the internal index of the read field value. * @throws IOException if the reading failed. */ int c(JsonReader jsonReader) throws IOException; } ================================================ FILE: base/src/main/java/proguard/optimize/gson/_OptimizedJsonReaderImpl.java ================================================ /* * 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 com.google.gson.stream.*; import java.io.IOException; import java.util.*; /** * This class is a template for an _OptimizedJsonReader implementation. * The data structure that contains the mapping between Json field names * and internal indices is empty and needs to be initialized using injected * byte code. * * @author Lars Vandenbergh */ public class _OptimizedJsonReaderImpl implements _OptimizedJsonReader { /* * The original name of this field is "names". * * The name of this field has already been obfuscated because it is part * of an injected class. * * When renaming this field, the corresponding constant in * OptimizedClassConstants needs to be updated accordingly. */ private static final Map a = a(); /* * Initializes the data structure containing the mapping between Json field * names and internal indices. * * The original name of this method is "initNames". * * The name of this method has already been obfuscated because it is part * of an injected class. * * When renaming this method, the corresponding constant in * OptimizedClassConstants needs to be updated accordingly. */ private static Map a() { return null; } // Implementations for _OptimizedJsonReader. @Override public int b(JsonReader jsonReader) throws IOException { String name = jsonReader.nextName(); Integer fieldIndex = a.get(name); return fieldIndex == null ? -1 : fieldIndex; } @Override public int c(JsonReader jsonReader) throws IOException { String value = jsonReader.nextString(); Integer valueIndex = a.get(value); return valueIndex == null ? -1 : valueIndex; } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/_OptimizedJsonWriter.java ================================================ /* * 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 com.google.gson.stream.JsonWriter; import java.io.IOException; /* * Interface for writing Json fields and values using an internal index. * This allows injecting optimized Java code that writes out Json without * referring to Json field names and values using plain Strings. * * @author Lars Vandenbergh */ public interface _OptimizedJsonWriter { /** * Writes the field name with the given internal index to the given Json writer. * * The original name of this method is "name". * * The name of this field has already been obfuscated because it is part * of an injected class. * * When renaming this field, the corresponding constant in * OptimizedClassConstants needs to be updated accordingly. * * @param jsonWriter the Json writer to write to. * @param nameIndex the internal index of the field name. * @throws IOException if the writing failed. */ void b(JsonWriter jsonWriter, int nameIndex) throws IOException; /** * Writes the field value with the given internal index to the given Json writer. * * The original name of this method is "value". * * The name of this method has already been obfuscated because it is part * of an injected class. * * When renaming this method, the corresponding constant in * OptimizedClassConstants needs to be updated accordingly. * * @param jsonWriter the Json writer to write to. * @param valueIndex the internal index of the field value. * @throws IOException if the writing failed. */ void c(JsonWriter jsonWriter, int valueIndex) throws IOException; } ================================================ FILE: base/src/main/java/proguard/optimize/gson/_OptimizedJsonWriterImpl.java ================================================ /* * 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 com.google.gson.stream.JsonWriter; import java.io.IOException; /** * This class is a template for an _OptimizedJsonWriter implementation. * The data structure that contains the mapping between internal indices * and Json field names is empty and needs to be initialized using injected * byte code. * * @author Lars Vandenbergh */ public class _OptimizedJsonWriterImpl implements _OptimizedJsonWriter { /* * The original name of this field is "names". * * The name of this field has already been obfuscated because it is part * of an injected class. * * When renaming this field, the corresponding constant in * OptimizedClassConstants needs to be updated accordingly. */ private static final String[] a = a(); /* * Initializes the data structure containing the mapping between internal * indices and Json field names. * * The original name of this method is "initNames". * * The name of this method has already been obfuscated because it is part * of an injected class. * * When renaming this method, the corresponding constant in * OptimizedClassConstants needs to be updated accordingly. */ private static String[] a() { return null; } // Implementations for _OptimizedJsonWriter. @Override public void b(JsonWriter jsonWriter, int nameIndex) throws IOException { jsonWriter.name(a[nameIndex]); } @Override public void c(JsonWriter jsonWriter, int valueIndex) throws IOException { jsonWriter.value(a[valueIndex]); } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/_OptimizedTypeAdapter.java ================================================ /* * 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; /** * Marker interface for all injected optimized GSON type adapters. This allows * recognizing these type adapters and giving them the right priority. * * @author Lars Vandenbergh */ public interface _OptimizedTypeAdapter { } ================================================ FILE: base/src/main/java/proguard/optimize/gson/_OptimizedTypeAdapterFactory.java ================================================ /* * 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 com.google.gson.*; import com.google.gson.reflect.TypeToken; /** * Template class for the optimized type adapter factory that deals with all * optimized GSON domain classes. * * The implementation of the create() method needs to be replaced with * injected byte code that handles the specific domain classes and returns * the appropriate optimized type adapter. * * @author Lars Vandenbergh */ public class _OptimizedTypeAdapterFactory implements TypeAdapterFactory { private static final _OptimizedJsonReaderImpl optimizedJsonReaderImpl = new _OptimizedJsonReaderImpl(); private static final _OptimizedJsonWriterImpl optimizedJsonWriterImpl = new _OptimizedJsonWriterImpl(); // Implementations for TypeAdapterFactory. @Override public TypeAdapter create(Gson gson, TypeToken type) { return null; } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/_OptimizedTypeAdapterImpl.java ================================================ /* * 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 com.google.gson.*; import com.google.gson.stream.*; import java.io.IOException; /** * Template class for an optimized GSON type adapter. * * The implementation of the write() and read() methods need to be replaced * with injected byte code that invokes the generated toJson$xxx() and * fromJson$xxx() methods on the appropriate domain class. */ public class _OptimizedTypeAdapterImpl extends TypeAdapter implements _OptimizedTypeAdapter { private Gson gson; private _OptimizedJsonReader optimizedJsonReader; private _OptimizedJsonWriter optimizedJsonWriter; /** * Creates a new _OptimizedTypeAdapterImpl. * * @param gson the Gson context. * @param optimizedJsonReader the optimized reader used to read Json. * @param optimizedJsonWriter the optimized writer used to write Json. */ public _OptimizedTypeAdapterImpl(Gson gson, _OptimizedJsonReader optimizedJsonReader, _OptimizedJsonWriter optimizedJsonWriter) { this.gson = gson; this.optimizedJsonReader = optimizedJsonReader; this.optimizedJsonWriter = optimizedJsonWriter; } // Implementations for TypeAdapter. @Override public void write(JsonWriter writer, Object value) throws IOException { } @Override public Object read(JsonReader reader) throws IOException { return null; } } ================================================ FILE: base/src/main/java/proguard/optimize/gson/package.html ================================================ This package contains classes for optimizing usages of the Gson library for serializing and deserialing Json. ================================================ FILE: base/src/main/java/proguard/optimize/info/AccessMethodMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.*; /** * This InstructionVisitor marks the types of class accesses and class member * accesses of the methods whose instructions it visits. * * @author Eric Lafortune */ public class AccessMethodMarker implements InstructionVisitor, ConstantVisitor, ClassVisitor, MemberVisitor { private Method invokingMethod; // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { invokingMethod = method; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Check the referenced class or class member, if any. stringConstant.referencedClassAccept(this); stringConstant.referencedMemberAccept(this); } public void visitDynamicConstant(Clazz clazz, DynamicConstant dynamicConstant) { // Check the bootstrap method. dynamicConstant.bootstrapMethodHandleAccept(clazz, this); } public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { // Check the bootstrap method. invokeDynamicConstant.bootstrapMethodHandleAccept(clazz, this); } public void visitMethodHandleConstant(Clazz clazz, MethodHandleConstant methodHandleConstant) { // Check the method reference. clazz.constantPoolEntryAccept(methodHandleConstant.u2referenceIndex, this); } public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { // Check the referenced class. clazz.constantPoolEntryAccept(refConstant.u2classIndex, this); // Check the referenced class member itself. refConstant.referencedClassAccept(this); refConstant.referencedMemberAccept(this); } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Check the referenced class. classConstant.referencedClassAccept(this); } // Implementations for ClassVisitor. public void visitAnyClass(Clazz clazz) { int accessFlags = clazz.getAccessFlags(); if ((accessFlags & AccessConstants.PUBLIC) == 0) { setAccessesPackageCode(invokingMethod); } } // Implementations for MemberVisitor. public void visitAnyMember(Clazz clazz, Member member) { int accessFlags = member.getAccessFlags(); if ((accessFlags & AccessConstants.PRIVATE) != 0) { setAccessesPrivateCode(invokingMethod); } else if ((accessFlags & AccessConstants.PROTECTED) != 0) { setAccessesProtectedCode(invokingMethod); } else if ((accessFlags & AccessConstants.PUBLIC) == 0) { setAccessesPackageCode(invokingMethod); } } // Small utility methods. private static void setAccessesPrivateCode(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setAccessesPrivateCode(); } /** * Returns whether the given method accesses private class members. */ public static boolean accessesPrivateCode(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).accessesPrivateCode(); } private static void setAccessesPackageCode(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setAccessesPackageCode(); } /** * Returns whether the given method accesses package visible classes or class * members. */ public static boolean accessesPackageCode(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).accessesPackageCode(); } private static void setAccessesProtectedCode(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setAccessesProtectedCode(); } /** * Returns whether the given method accesses protected class members. */ public static boolean accessesProtectedCode(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).accessesProtectedCode(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/BackwardBranchMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This InstructionVisitor marks all methods that branch backward in any of the * instructions that it visits. * * @author Eric Lafortune */ public class BackwardBranchMarker implements InstructionVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { markBackwardBranch(method, branchInstruction.branchOffset); } public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction) { markBackwardBranch(method, switchInstruction.defaultOffset); for (int index = 0; index < switchInstruction.jumpOffsets.length; index++) { markBackwardBranch(method, switchInstruction.jumpOffsets[index]); } } // Small utility methods. /** * Marks the given method if the given branch offset is negative. */ private void markBackwardBranch(Method method, int branchOffset) { if (branchOffset < 0) { setBranchesBackward(method); } } private static void setBranchesBackward(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setBranchesBackward(); } public static boolean branchesBackward(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).branchesBackward(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/CatchExceptionMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; /** * This AttributeVisitor marks all methods that catch exceptions. * * @author Eric Lafortune */ public class CatchExceptionMarker implements AttributeVisitor { // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { if (codeAttribute.u2exceptionTableLength > 0) { markCatchException(method); } } // Small utility methods. private static void markCatchException(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setCatchesExceptions(); } public static boolean catchesExceptions(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).catchesExceptions(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/CaughtClassFilter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates all its method calls to another ClassVisitor, * but only for Clazz objects that are caught as exceptions. * * @see CaughtClassMarker * @author Eric Lafortune */ public class CaughtClassFilter implements ClassVisitor { private final ClassVisitor classVisitor; public CaughtClassFilter(ClassVisitor classVisitor) { this.classVisitor = classVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { if (CaughtClassMarker.isCaught(clazz)) { clazz.accept(classVisitor); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/CaughtClassMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor marks all program classes that it visits as caught. * This means that these classes are exception classes that occur in exception * handlers. * * @author Eric Lafortune */ public class CaughtClassMarker implements ClassVisitor { // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { setCaught(programClass); } // Small utility methods. private static void setCaught(Clazz clazz) { ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setCaught(); } public static boolean isCaught(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).isCaught(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ClassOptimizationInfo.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.Clazz; /** * This class stores some optimization information that can be attached to * a class. * * @author Eric Lafortune */ public class ClassOptimizationInfo { protected boolean hasNoSideEffects = false; /** * Specifies that loading the class has no side effects. */ public void setNoSideEffects() { hasNoSideEffects = true; } /** * Returns whether loading the class has side effects. */ public boolean hasNoSideEffects() { return hasNoSideEffects; } /** * Returns whether the class is kept. */ // TODO: This information is now available from the processing flags. public boolean isKept() { return true; } public boolean containsConstructors() { return true; } /** * Returns whether the class is instantiated in the known code. */ public boolean isInstantiated() { return true; } /** * Returns whether the class is part of an 'instanceof' instruction in the * known code. */ public boolean isInstanceofed() { // We're relaxing the strict assumption of "true". return !hasNoSideEffects; } /** * Returns whether the class is loaded with an 'ldc' instruction (a .class * construct in Java) in the known code. */ public boolean isDotClassed() { // We're relaxing the strict assumption of "true". return !hasNoSideEffects; } /** * Returns whether the class is a Throwable that is caught in an exception * handler in the known code. */ public boolean isCaught() { return true; } /** * Returns whether the class is an enum type that can be simplified to a * primitive integer. */ public boolean isSimpleEnum() { return false; } /** * Returns whether instances of the class are ever escaping to the heap. * Otherwise, any instances are just created locally and passed as * parameters. */ public boolean isEscaping() { return true; } /** * Returns whether loading the class has any side effects. */ public boolean hasSideEffects() { return !hasNoSideEffects; } /** * Returns whether the class contains any package visible class members. */ public boolean containsPackageVisibleMembers() { return true; } /** * Returns whether any code in the class accesses any package visible * class members. */ public boolean invokesPackageVisibleMembers() { return true; } /** * Returns whether the class may be merged with other classes. */ public boolean mayBeMerged() { return false; } /** * Returns the class for which this class is a simple wrapper without any * additional functionality, or null otherwise. */ public Clazz getWrappedClass() { return null; } /** * Returns the class into which this class can be merged. */ public Clazz getTargetClass() { return null; } /** * Creates and sets a ClassOptimizationInfo instance on the given class. */ public static void setClassOptimizationInfo(Clazz clazz) { clazz.setProcessingInfo(new ClassOptimizationInfo()); } /** * Returns the ClassOptimizationInfo instance from the given class. */ public static ClassOptimizationInfo getClassOptimizationInfo(Clazz clazz) { return (ClassOptimizationInfo)clazz.getProcessingInfo(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/CodeAttributeOptimizationInfo.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.attribute.CodeAttribute; /** * This class stores some optimization information that can be attached * to a code attribute. * * @author Thomas Neidhart */ public class CodeAttributeOptimizationInfo { public boolean isKept() { return true; } public static void setCodeAttributeOptimizationInfo(CodeAttribute codeAttribute) { codeAttribute.setProcessingInfo(new CodeAttributeOptimizationInfo()); } public static CodeAttributeOptimizationInfo getCodeAttributeOptimizationInfo(CodeAttribute codeAttribute) { return (CodeAttributeOptimizationInfo)codeAttribute.getProcessingInfo(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ContainsConstructorsMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.*; import proguard.classfile.visitor.*; /** * This MemberVisitor marks all classes that contain any kind of constructors. * * @author Joachim Vandersmissen */ public class ContainsConstructorsMarker implements MemberVisitor { // Implementations for MemberVisitor. public void visitAnyMember(Clazz clazz, Member member) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_INIT)) { ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(programClass).setContainsConstructors(); } } public static boolean containsConstructors(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).containsConstructors(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/DotClassFilter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates all its method calls to another ClassVisitor, * but only for Clazz objects that are used in a .class construct. * * @see DotClassMarker * @author Eric Lafortune */ public class DotClassFilter implements ClassVisitor { private final ClassVisitor classVisitor; public DotClassFilter(ClassVisitor classVisitor) { this.classVisitor = classVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { if (DotClassMarker.isDotClassed(clazz)) { clazz.accept(classVisitor); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/DotClassMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.ClassVisitor; import proguard.optimize.OptimizationInfoClassFilter; /** * This InstructionVisitor marks all classes that are used in a .class * construct by any of the instructions that it visits. * * @author Eric Lafortune */ public class DotClassMarker implements InstructionVisitor, ConstantVisitor, ClassVisitor { private final OptimizationInfoClassFilter filteredClassMarker = new OptimizationInfoClassFilter(this); // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_LDC || constantInstruction.opcode == Instruction.OP_LDC_W) { clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { classConstant.referencedClassAccept(filteredClassMarker); } // Implementations for ClassVisitor. public void visitAnyClass(Clazz clazz) { setDotClassed(clazz); } // Small utility methods. private static void setDotClassed(Clazz clazz) { ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setDotClassed(); } public static boolean isDotClassed(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).isDotClassed(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/DynamicInvocationMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.*; /** * This InstructionVisitor marks whether the methods whose instructions it * visits contain the invokedynamic instruction. * * @author Eric Lafortune */ public class DynamicInvocationMarker implements InstructionVisitor, ConstantVisitor, MemberVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_INVOKEDYNAMIC) { setInvokesDynamically(method); } } // Small utility methods. private static void setInvokesDynamically(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setInvokesDynamically(); } /** * Returns whether the given method calls the invokedynamic instruction. */ public static boolean invokesDynamically(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).invokesDynamically(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/EscapingClassFilter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates its visits to one of two other given * ClassVisitor instances, depending on whether the classes are marked to be * escaping or not. * * @see EscapingClassMarker * * @author Eric Lafortune */ public class EscapingClassFilter implements ClassVisitor { private final ClassVisitor escapingClassVisitor; private final ClassVisitor otherClassVisitor; /** * Creates a new EscapingClassFilter. * @param escapingClassVisitor the class visitor to which visits to * classes that are marked to be escaping * will be delegated. */ public EscapingClassFilter(ClassVisitor escapingClassVisitor) { this(escapingClassVisitor, null); } /** * Creates a new EscapingClassFilter. * @param escapingClassVisitor the class visitor to which visits to * classes that are marked to be escaping * will be delegated. * @param otherClassVisitor the class visitor to which visits to * classes that are not marked to be escaping * will be delegated. */ public EscapingClassFilter(ClassVisitor escapingClassVisitor, ClassVisitor otherClassVisitor) { this.escapingClassVisitor = escapingClassVisitor; this.otherClassVisitor = otherClassVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { // Is the class marked to be escaping? ClassVisitor classVisitor = EscapingClassMarker.isClassEscaping(clazz) ? escapingClassVisitor : otherClassVisitor; if (classVisitor != null) { clazz.accept(classVisitor); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/EscapingClassMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.ClassVisitor; import proguard.evaluation.*; import proguard.evaluation.value.*; /** * This AttributeVisitor marks the classes that are escaping from the visited * code attributes. * * @see ReferenceEscapeChecker * @author Eric Lafortune */ public class EscapingClassMarker implements AttributeVisitor, InstructionVisitor, ClassVisitor { private static final Logger logger = LogManager.getLogger(EscapingClassMarker.class); private final PartialEvaluator partialEvaluator; private final boolean runPartialEvaluator; private final ReferenceEscapeChecker referenceEscapeChecker; private final boolean runReferenceEscapeChecker; /** * Creates a new EscapingClassMarker. */ public EscapingClassMarker() { // We need typed references. this(new TypedReferenceValueFactory()); } /** * Creates a new EscapingClassMarker. */ public EscapingClassMarker(ValueFactory valueFactory) { this(new ReferenceTracingValueFactory(valueFactory)); } /** * Creates a new EscapingClassMarker. */ public EscapingClassMarker(ReferenceTracingValueFactory tracingValueFactory) { this(new PartialEvaluator(tracingValueFactory, new ReferenceTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)), true, tracingValueFactory), true); } /** * Creates a new EscapingClassMarker. */ public EscapingClassMarker(PartialEvaluator partialEvaluator, boolean runPartialEvaluator) { this(partialEvaluator, runPartialEvaluator, new ReferenceEscapeChecker(partialEvaluator, false), true); } /** * Creates a new EscapingClassMarker. */ public EscapingClassMarker(PartialEvaluator partialEvaluator, boolean runPartialEvaluator, ReferenceEscapeChecker referenceEscapeChecker, boolean runReferenceEscapeChecker) { this.partialEvaluator = partialEvaluator; this.runPartialEvaluator = runPartialEvaluator; this.referenceEscapeChecker = referenceEscapeChecker; this.runReferenceEscapeChecker = runReferenceEscapeChecker; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Evaluate the code. if (runPartialEvaluator) { partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); } if (runReferenceEscapeChecker) { referenceEscapeChecker.visitCodeAttribute(clazz, method, codeAttribute); } // Mark all escaping classes. codeAttribute.instructionsAccept(clazz, method, partialEvaluator.tracedInstructionFilter(this)); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // Does the instruction push a value that escapes? // We'll also count values that are returned, since they may be // downcast and the downcast type may escape in some calling // method. // TODO: Refine check: is a value is downcast to an escaping class, while it is being returned? if (instruction.stackPushCount(clazz) == 1 && (referenceEscapeChecker.isInstanceEscaping(offset) || referenceEscapeChecker.isInstanceReturned(offset))) { TracedStack stackAfter = partialEvaluator.getStackAfter(offset); Value stackEntry = stackAfter.getTop(0); // Is it really a reference type? if (stackEntry.computationalType() == Value.TYPE_REFERENCE) { // Is it a plain class reference type? ReferenceValue referenceValue = stackEntry.referenceValue(); if (referenceValue.isNull() != Value.ALWAYS && !ClassUtil.isInternalArrayType(referenceValue.getType())) { // Do we know the class? Clazz referencedClass = referenceValue.getReferencedClass(); if (referencedClass != null) { logger.debug("EscapingClassMarker: [{}.{}{}]: {} pushes escaping [{}]", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), instruction.toString(offset), referencedClass.getName() ); // Mark it, along with its superclasses. referencedClass.hierarchyAccept(true, true, true, false, this); } } } } } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { markClassEscaping(programClass); } // Small utility methods. /** * Marks the given class as escaping. */ private void markClassEscaping(Clazz clazz) { ClassOptimizationInfo info = ProgramClassOptimizationInfo.getClassOptimizationInfo(clazz); if (info instanceof ProgramClassOptimizationInfo) { ((ProgramClassOptimizationInfo)info).setEscaping(); } } /** * Returns whether the given class is escaping. */ public static boolean isClassEscaping(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).isEscaping(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ExceptionInstructionChecker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This class can tell whether an instruction might throw exceptions. * * @author Eric Lafortune */ public class ExceptionInstructionChecker implements InstructionVisitor // ConstantVisitor, // MemberVisitor { private static final Logger logger = LogManager.getLogger(ExceptionInstructionChecker.class); // A return value for the visitor methods. private boolean mayThrowExceptions; /** * Returns whether the specified method may throw exceptions. */ public boolean mayThrowExceptions(Clazz clazz, Method method, CodeAttribute codeAttribute) { return mayThrowExceptions(clazz, method, codeAttribute, 0, codeAttribute.u4codeLength); } /** * Returns whether the specified block of code may throw exceptions. */ public boolean mayThrowExceptions(Clazz clazz, Method method, CodeAttribute codeAttribute, int startOffset, int endOffset) { logger.debug("ExceptionInstructionChecker.mayThrowExceptions [{}.{}{}]: {} -> {}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), startOffset, endOffset ); return firstExceptionThrowingInstructionOffset(clazz, method, codeAttribute, startOffset, endOffset) < endOffset; } /** * Returns the offset of the first instruction in the specified block of * code that may throw exceptions, or the end offset if there is none. */ public int firstExceptionThrowingInstructionOffset(Clazz clazz, Method method, CodeAttribute codeAttribute, int startOffset, int endOffset) { logger.debug("ExceptionInstructionChecker.firstExceptionThrowingInstructionOffset [{}.{}{}]: {} -> {}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), startOffset, endOffset ); byte[] code = codeAttribute.code; // Go over all instructions. int offset = startOffset; while (offset < endOffset) { // Get the current instruction. Instruction instruction = InstructionFactory.create(code, offset); // Check if it may be throwing exceptions. if (mayThrowExceptions(clazz, method, codeAttribute, offset, instruction)) { logger.debug(" {}", instruction.toString(offset)); return offset; } // Go to the next instruction. offset += instruction.length(offset); } return endOffset; } /** * Returns the offset after the last instruction in the specified block of * code that may throw exceptions, or the start offset if there is none. */ public int lastExceptionThrowingInstructionOffset(Clazz clazz, Method method, CodeAttribute codeAttribute, int startOffset, int endOffset) { logger.debug("ExceptionInstructionChecker.lastExceptionThrowingInstructionOffset [{}.{}{}]: {} -> {}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), startOffset, endOffset ); byte[] code = codeAttribute.code; int lastOffset = startOffset; // Go over all instructions. int offset = startOffset; while (offset < endOffset) { // Get the current instruction. Instruction instruction = InstructionFactory.create(code, offset); // Check if it may be throwing exceptions. if (mayThrowExceptions(clazz, method, codeAttribute, offset, instruction)) { logger.debug(" {}", instruction.toString(offset)); // Go to the next instruction. offset += instruction.length(offset); lastOffset = offset; } else { // Go to the next instruction. offset += instruction.length(offset); } } return lastOffset; } /** * Returns whether the specified instruction may throw exceptions. */ public boolean mayThrowExceptions(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset) { Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); return mayThrowExceptions(clazz, method, codeAttribute, offset, instruction); } /** * Returns whether the given instruction may throw exceptions. */ public boolean mayThrowExceptions(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { return instruction.mayInstanceThrowExceptions(clazz); // mayThrowExceptions = false; // // instruction.accept(clazz, method, codeAttribute, offset, this); // // return mayThrowExceptions; } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { // Check for instructions that may throw exceptions. // Note that monitorexit can not sensibly throw exceptions, except the // broken and deprecated asynchronous ThreadDeath. Removing the // artificial infinite looping exception blocks that recent compilers // add does not strictly follow the JVM specs, but it does have the // additional benefit of avoiding a bug in the JVM in JDK 1.1. switch (simpleInstruction.opcode) { case Instruction.OP_IDIV: case Instruction.OP_LDIV: case Instruction.OP_IREM: case Instruction.OP_LREM: case Instruction.OP_IALOAD: case Instruction.OP_LALOAD: case Instruction.OP_FALOAD: case Instruction.OP_DALOAD: case Instruction.OP_AALOAD: case Instruction.OP_BALOAD: case Instruction.OP_CALOAD: case Instruction.OP_SALOAD: case Instruction.OP_IASTORE: case Instruction.OP_LASTORE: case Instruction.OP_FASTORE: case Instruction.OP_DASTORE: case Instruction.OP_AASTORE: case Instruction.OP_BASTORE: case Instruction.OP_CASTORE: case Instruction.OP_SASTORE: case Instruction.OP_NEWARRAY: case Instruction.OP_ARRAYLENGTH: case Instruction.OP_ATHROW: case Instruction.OP_MONITORENTER: // These instructions may throw exceptions. mayThrowExceptions = true; } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { // Check for instructions that may throw exceptions. switch (constantInstruction.opcode) { case Instruction.OP_GETSTATIC: case Instruction.OP_PUTSTATIC: case Instruction.OP_GETFIELD: case Instruction.OP_PUTFIELD: case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: case Instruction.OP_INVOKEINTERFACE: case Instruction.OP_INVOKEDYNAMIC: case Instruction.OP_NEW: case Instruction.OP_ANEWARRAY: case Instruction.OP_CHECKCAST: case Instruction.OP_INSTANCEOF: case Instruction.OP_MULTIANEWARRAY: // These instructions may throw exceptions. mayThrowExceptions = true; // case Instruction.OP_INVOKEVIRTUAL: // case Instruction.OP_INVOKESPECIAL: // case Instruction.OP_INVOKESTATIC: // case Instruction.OP_INVOKEINTERFACE: // // Check if the invoking the method may throw an exception. // clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } } // // Implementations for ConstantVisitor. // // public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) // { // Member referencedMember = anyMethodrefConstant.referencedMethod; // // // Do we have a reference to the method? // if (referencedMember == null) // { // // We'll have to assume invoking the unknown method may throw an // // an exception. // mayThrowExceptions = true; // } // else // { // // First check the referenced method itself. // anyMethodrefConstant.referencedMethodAccept(this); // // // If the result isn't conclusive, check down the hierarchy. // if (!mayThrowExceptions) // { // Clazz referencedClass = anyMethodrefConstant.referencedClass; // Method referencedMethod = (Method)referencedMember; // // // Check all other implementations of the method in the class // // hierarchy. // referencedClass.methodImplementationsAccept(referencedMethod, // false, // false, // true, // true, // this); // } // } // } // // // // Implementations for MemberVisitor. // // public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) // { // mayThrowExceptions = mayThrowExceptions || // ExceptionMethodMarker.mayThrowExceptions(programMethod); // } // // // public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) // { // mayThrowExceptions = mayThrowExceptions || // !NoExceptionMethodMarker.doesntThrowExceptions(libraryMethod); // } } ================================================ FILE: base/src/main/java/proguard/optimize/info/FieldOptimizationInfo.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.*; import proguard.evaluation.value.*; /** * This class stores some optimization information that can be attached to * a field. * * @author Eric Lafortune */ public class FieldOptimizationInfo { protected Value value; /** * Returns whether the method is kept. */ // TODO: This information is now available from the processing flags. public boolean isKept() { return true; } /** * Returns whether the field is ever written to. */ public boolean isWritten() { return true; } /** * Returns whether the field is ever read. */ public boolean isRead() { return true; } /** * Returns whether the field can be made private. */ public boolean canBeMadePrivate() { return false; } /** * Returns a representation of the class through which the field is * accessed, or null if it is unknown. */ public ReferenceValue getReferencedClass() { return null; } /** * Specifies the value of the field. */ public void setValue(Value value) { this.value = value; } /** * Returns a representation of the value of the field, or null * if it is unknown. */ public Value getValue() { return value; } /** * Creates and sets a FieldOptimizationInfo instance on the given field. */ public static void setFieldOptimizationInfo(Clazz clazz, Field field) { field.setProcessingInfo(new FieldOptimizationInfo()); } /** * Returns the FieldOptimizationInfo instance from the given field. */ public static FieldOptimizationInfo getFieldOptimizationInfo(Field field) { return (FieldOptimizationInfo)field.getProcessingInfo(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/FinalFieldAssignmentMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This InstructionVisitor marks whether a final field is assigned * in the methods whose instructions it visits. * * @author Thomas Neidhart */ public class FinalFieldAssignmentMarker implements InstructionVisitor, ConstantVisitor { private Method referencedMethod; // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_PUTSTATIC || constantInstruction.opcode == Instruction.OP_PUTFIELD) { referencedMethod = method; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } } // Implementations for ConstantVisitor. @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} @Override public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { if (fieldrefConstant.referencedField != null && (fieldrefConstant.referencedField.getAccessFlags() & AccessConstants.FINAL) != 0) { setAssignsFinalField(referencedMethod); } } // Small utility methods. private static void setAssignsFinalField(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setAssignsFinalField(); } /** * Returns whether the given method assigns a final field. */ public static boolean assignsFinalField(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).assignsFinalField(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/InstanceofClassFilter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates all its method calls to another ClassVisitor, * but only for Clazz objects that are used in an 'instanceof' test. * * @see InstanceofClassMarker * @author Eric Lafortune */ public class InstanceofClassFilter implements ClassVisitor { private final ClassVisitor classVisitor; public InstanceofClassFilter(ClassVisitor classVisitor) { this.classVisitor = classVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { if (InstanceofClassMarker.isInstanceofed(clazz)) { clazz.accept(classVisitor); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/InstanceofClassMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.ClassVisitor; import proguard.optimize.OptimizationInfoClassFilter; /** * This InstructionVisitor marks all classes that are used in an 'instanceof' * test by any of the instructions that it visits. * * @author Eric Lafortune */ public class InstanceofClassMarker implements InstructionVisitor, ConstantVisitor, ClassVisitor { private final OptimizationInfoClassFilter filteredClassMarker = new OptimizationInfoClassFilter(this); // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_INSTANCEOF) { clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } } // Implementations for ConstantVisitor. public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { classConstant.referencedClassAccept(filteredClassMarker); } // Implementations for ClassVisitor. public void visitAnyClass(Clazz clazz) { setInstanceofed(clazz); } // Small utility methods. private static void setInstanceofed(Clazz clazz) { ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setInstanceofed(); } public static boolean isInstanceofed(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).isInstanceofed(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/InstantiationClassFilter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates all its method calls to another ClassVisitor, * but only for Clazz objects that are instantiated. * * @author Eric Lafortune */ public class InstantiationClassFilter implements ClassVisitor { private final ClassVisitor classVisitor; public InstantiationClassFilter(ClassVisitor classVisitor) { this.classVisitor = classVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { if (InstantiationClassMarker.isInstantiated(clazz)) { clazz.accept(classVisitor); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/InstantiationClassMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.*; import proguard.optimize.OptimizationInfoClassFilter; /** * This InstructionVisitor marks all classes that are instantiated by any of * the instructions that it visits. * * @author Eric Lafortune */ public class InstantiationClassMarker implements InstructionVisitor, ConstantVisitor, ClassVisitor { private final OptimizationInfoClassFilter filteredClassMarker = new OptimizationInfoClassFilter(this); // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_NEW) { clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } } // Implementations for ConstantVisitor. public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { classConstant.referencedClassAccept( new ClassHierarchyTraveler(true, true, false, false, filteredClassMarker)); } // Implementations for ClassVisitor. public void visitAnyClass(Clazz clazz) { setInstantiated(clazz); } // Small utility methods. private static void setInstantiated(Clazz clazz) { ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setInstantiated(); } public static boolean isInstantiated(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).isInstantiated(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/MethodInvocationMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.OptimizationInfoMemberFilter; /** * This InstructionVisitor counts the number of times methods are invoked from * the instructions that are visited. * * @author Eric Lafortune */ public class MethodInvocationMarker implements InstructionVisitor, ConstantVisitor, MemberVisitor { private final OptimizationInfoMemberFilter filteredMethodMarker = new OptimizationInfoMemberFilter(this); // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Mark the referenced method, if any. stringConstant.referencedMemberAccept(filteredMethodMarker); } public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { // Mark the referenced method. anyMethodrefConstant.referencedMethodAccept(filteredMethodMarker); } // Implementations for MemberVisitor. public void visitAnyMember(Clazz Clazz, Member member) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { incrementInvocationCount(programMethod); } // Small utility methods. private static void incrementInvocationCount(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).incrementInvocationCount(); } /** * Returns the number of times the given method was invoked by the * instructions that were visited. */ public static int getInvocationCount(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).getInvocationCount(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/MethodOptimizationInfo.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.*; import proguard.classfile.util.MethodLinker; import proguard.evaluation.value.Value; /** * This class stores some optimization information that can be attached to * a method. * * @author Eric Lafortune */ public class MethodOptimizationInfo { protected boolean hasNoSideEffects = false; protected boolean hasNoExternalSideEffects = false; protected boolean hasNoEscapingParameters = false; protected boolean hasNoExternalReturnValues = false; protected Value returnValue = null; /** * Returns whether the method is kept. */ // TODO: This information is now available from the processing flags. public boolean isKept() { return true; } /** * Specifies that the method has no side effects. Side effects include * changing static fields, changing instance fields, changing objects on * the heap, calling other methods with side effects, etc. */ public void setNoSideEffects() { hasNoSideEffects = true; hasNoExternalSideEffects = true; hasNoEscapingParameters = true; } /** * Specifies that the method has no side effects. Side effects include * changing static fields, changing instance fields, changing objects on * the heap, calling other methods with side effects, etc. */ public boolean hasNoSideEffects() { return hasNoSideEffects; } /** * Specifies that the method has no external side effects. External side * effects include changing static fields and generally changing objects * on the heap, but not changing fields of the instance on which the * method is called (or the class, in case of static methods), or any * instances that can only be reached through the instance. For example, * the append methods of StringBuilder have side effects, but no external * side effects. */ public void setNoExternalSideEffects() { hasNoExternalSideEffects = true; hasNoEscapingParameters = true; } /** * Retruns whether the method has no external side effects. External side * effects include changing static fields and generally changing objects * on the heap, but not changing fields of the instance on which the * method is called (or the class, in case of static methods), or any * instances that can only be reached through the instance. For example, * StringBuilder#append has side effects, but no external side effects. */ public boolean hasNoExternalSideEffects() { return hasNoExternalSideEffects; } /** * Specifies that the method doesn't have escaping parameters. Escaping * parameters are reference parameters that the method has made reachable * on the heap after the method has exited. For example, System#setProperty * and Set#add let their parameters escape, because they become reachable. */ public void setNoEscapingParameters() { hasNoEscapingParameters = true; } /** * Returns whether the method doesn't have escaping parameters. Escaping * parameters are reference parameters that the method has made reachable * on the heap after the method has exited. For example, System#setProperty * and Set#add let their parameters escape, because they become reachable. */ public boolean hasNoEscapingParameters() { return hasNoEscapingParameters; } /** * Specifies that the method doesn't return external values. External * return values are reference values that originate from the heap, but * not parameters or new instances. For example, Map#get has external * return values, but StringBuilder#toString has no external return * values. */ public void setNoExternalReturnValues() { hasNoExternalReturnValues = true; } /** * Returns whether the method returns external values. External return * values are reference values that originate from the heap, but not * parameters or new instances. For example, Map#get has external return * values, but StringBuilder#toString has no external return values. */ public boolean hasNoExternalReturnValues() { return hasNoExternalReturnValues; } /** * Specifies the return value of the method. */ public void setReturnValue(Value returnValue) { this.returnValue = returnValue; } /** * Returns a representation of the return value of the method, or null * if it is unknown. */ public Value getReturnValue() { return returnValue; } // Methods that may be specialized. /** * Returns whether the method has side effects. Side effects include * changing static fields, changing instance fields, changing objects on * the heap, calling other methods with side effects, etc. */ public boolean hasSideEffects() { return !hasNoSideEffects; } /** * Returns whether the method can be made private. */ public boolean canBeMadePrivate() { return false; } /** * Returns whether the method body contains any exception handlers. */ public boolean catchesExceptions() { return true; } /** * Returns whether the method body contains any backward branches. */ public boolean branchesBackward() { return true; } /** * Returns whether the method body invokes any super methods. */ public boolean invokesSuperMethods() { return true; } /** * Returns whether the method body invokes any methods with * 'invokedynamic'. */ public boolean invokesDynamically() { return true; } /** * Returns whether the method body accesses any private fields or methods. */ public boolean accessesPrivateCode() { return true; } /** * Returns whether the method body accesses any package visible fields or * methods. */ public boolean accessesPackageCode() { return true; } /** * Returns whether the method body accesses any protected fields or methods. */ public boolean accessesProtectedCode() { return true; } /** * Returns whether the method body contains any synchronization code * ('monitorenter' and 'monitorexit'). */ public boolean hasSynchronizedBlock() { return true; } /** * Returns whether the method body assigns any values to final fields. */ public boolean assignsFinalField() { return true; } /** * Returns whether the method body contains `return` instructions that * leave a non-empty stack. */ public boolean returnsWithNonEmptyStack() { return false; } /** * Returns the number of times the method is invoked in the known code * base. */ public int getInvocationCount() { return Integer.MAX_VALUE; } /** * Returns the size that the parameters of the method take up on the stack. * The size takes into account long and double parameters taking up two * entries. */ public int getParameterSize() { return 0; } /** * Returns whether the method has any unused parameters. */ public boolean hasUnusedParameters() { return false; } /** * Returns whether the method actually uses the specified parameter. * * The variable index takes into account long and double parameters * taking up two entries. */ public boolean isParameterUsed(int variableIndex) { return true; } /** * Returns a mask with the parameters that the method actually uses. * * The indices are variable indices of the variables. They take into * account long and double parameters taking up two entries. */ public long getUsedParameters() { return -1L; } /** * Returns whether the specified reference parameter has already escaped * to the heap when entering the method. * * The parameter index is based on the method descriptor, including 'this', * with each parameter having the same size. */ public boolean hasParameterEscaped(int parameterIndex) { return true; } /** * Returns a mask with the reference parameters have already escaped to * the heap when entering the method. * * The parameter index is based on the method descriptor, including 'this', * with each parameter having the same size. */ public long getEscapedParameters() { return -1L; } /** * Returns whether the specified parameter escapes from the method. * An escaping parameter is a reference parameter that the method has made * reachable on the heap after the method has exited. For example, * System#setProperty and Set#add let their parameters escape, because * they become reachable. * * The parameter index is based on the method descriptor, with each * with each parameter having the same size. */ public boolean isParameterEscaping(int parameterIndex) { return !hasNoEscapingParameters; } /** * Returns a mask with the parameters that escape from the method. * Escaping parameters are reference parameters that the method has made * reachable on the heap after the method has exited. For example, * System#setProperty and Set#add let their parameters escape, because * they become reachable. * * The parameter index is based on the method descriptor, including 'this', * with each parameter having the same size. */ public long getEscapingParameters() { return hasNoEscapingParameters ? 0L : -1L; } /** * Returns whether the contents of the specified reference parameter are * modified in the method. * * The parameter index is based on the method descriptor, with each * with each parameter having the same size. */ public boolean isParameterModified(int parameterIndex) { // TODO: Refine for static methods. return !hasNoSideEffects && (!hasNoExternalSideEffects || parameterIndex == 0); } /** * Returns a mask of the reference parameters whose contents are modified * in the method. * * The parameter index is based on the method descriptor, including 'this', * with each parameter having the same size. */ public long getModifiedParameters() { // TODO: Refine for static methods. return hasNoSideEffects ? 0L : hasNoExternalSideEffects ? 1L : -1L; } /** * Returns whether the method might modify objects that it can reach * through static fields or instance fields. */ public boolean modifiesAnything() { return !hasNoExternalSideEffects; } /** * Returns a representation of the specified method parameter, or null * if it is unknown. * * The parameter index is based on the method descriptor, with each * with each parameter having the same size. */ public Value getParameterValue(int parameterIndex) { return null; } /** * Returns whether the method might return the specified reference * parameter as its result. * * The parameter index is based on the method descriptor, with each * with each parameter having the same size. */ public boolean returnsParameter(int parameterIndex) { return true; } /** * Returns a mask of the reference parameters that the method might return. * * The parameter index is based on the method descriptor, including 'this', * with each parameter having the same size. */ public long getReturnedParameters() { return -1L; } /** * Returns whether the method might create and return new instances. */ public boolean returnsNewInstances() { return true; } /** * Returns whether the method might return external values. External * return values are reference values that originate from the heap, but * not parameters or new instances. For example, Map#get has external * return values, but StringBuilder#toString has no external return * values. */ public boolean returnsExternalValues() { return !hasNoExternalReturnValues; } /** * Creates and sets a MethodOptimizationInfo instance on the specified * chain of linked methods. */ public static void setMethodOptimizationInfo(Clazz clazz, Method method) { MethodLinker.lastMember(method).setProcessingInfo(new MethodOptimizationInfo()); } /** * Returns the MethodOptimizationInfo instance from the specified chain of * linked methods. */ public static MethodOptimizationInfo getMethodOptimizationInfo(Method method) { return (MethodOptimizationInfo)MethodLinker.lastMember(method).getProcessingInfo(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/MutableBoolean.java ================================================ /* * 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.info; /** * This class provides a mutable boolean flag. */ public class MutableBoolean { private boolean flag; /* private int resetCounter; private int setCounter; private int totalCounter; //*/ public void set() { flag = true; /* System.out.println("MutableBoolean.set: "+resetCounter+", "+setCounter++ +", "+totalCounter++); if (totalCounter > 5000) { Thread.dumpStack(); } //*/ } public void reset() { flag = false; /* resetCounter++; setCounter = 0; //*/ } public boolean isSet() { return flag; } } ================================================ FILE: base/src/main/java/proguard/optimize/info/NoEscapingParametersMethodMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor marks all methods that it visits as not having any * escaping parameters (including 'this'). It will make the * ParameterEscapeMarker consider them as such without further analysis. * * @see ParameterEscapeMarker * @author Eric Lafortune */ public class NoEscapingParametersMethodMarker implements MemberVisitor { // Implementations for MemberVisitor. public void visitAnyMember(Clazz Clazz, Member member) { // Ignore any attempts to mark fields. } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { markNoParameterEscaping(programMethod); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { markNoParameterEscaping(libraryMethod); } // Small utility methods. private static void markNoParameterEscaping(Method method) { MethodOptimizationInfo.getMethodOptimizationInfo(method).setNoEscapingParameters(); } public static boolean hasNoParameterEscaping(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).hasNoEscapingParameters(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/NoExternalReturnValuesMethodMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor marks all methods that it visits as not having any * return values that are external reference values (only parameters or new * instances). It will make the ParameterEscapeMarker consider them as * such without further analysis. * * @see ParameterEscapeMarker * @author Eric Lafortune */ public class NoExternalReturnValuesMethodMarker implements MemberVisitor { // Implementations for MemberVisitor. public void visitAnyMember(Clazz Clazz, Member member) { // Ignore any attempts to mark fields. } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { markNoExternalReturnValues(programMethod); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { markNoExternalReturnValues(libraryMethod); } // Small utility methods. private static void markNoExternalReturnValues(Method method) { MethodOptimizationInfo.getMethodOptimizationInfo(method).setNoExternalReturnValues(); } public static boolean hasNoExternalReturnValues(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).hasNoExternalReturnValues(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/NoExternalSideEffectMethodMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor marks all methods that it visits as not having any * external side effects. It will make the SideEffectMethodMarker consider them * as such without further analysis. * * @see SideEffectMethodMarker * @author Eric Lafortune */ public class NoExternalSideEffectMethodMarker implements MemberVisitor { // Implementations for MemberVisitor. public void visitAnyMember(Clazz Clazz, Member member) { // Ignore any attempts to mark fields. } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { markNoExternalSideEffects(programMethod); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { markNoExternalSideEffects(libraryMethod); } // Small utility methods. private static void markNoExternalSideEffects(Method method) { MethodOptimizationInfo.getMethodOptimizationInfo(method).setNoExternalSideEffects(); } public static boolean hasNoExternalSideEffects(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).hasNoExternalSideEffects(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/NoSideEffectClassMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.Clazz; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor marks all classes that it visits as not having any side * effects. It will make the SideEffectClassMarker consider them as such * without further analysis. * * @see SideEffectMethodMarker * @author Eric Lafortune */ public class NoSideEffectClassMarker implements ClassVisitor { // Implementations for MemberVisitor. public void visitAnyClass(Clazz clazz) { markNoSideEffects(clazz); } // Small utility methods. private static void markNoSideEffects(Clazz clazz) { ClassOptimizationInfo.getClassOptimizationInfo(clazz).setNoSideEffects(); } public static boolean hasNoSideEffects(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).hasNoSideEffects(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/NoSideEffectMethodMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor marks all methods that it visits as not having any side * effects. It will make the SideEffectMethodMarker consider them as such * without further analysis. * * @see SideEffectMethodMarker * @author Eric Lafortune */ public class NoSideEffectMethodMarker implements MemberVisitor { // Implementations for MemberVisitor. public void visitAnyMember(Clazz Clazz, Member member) { // Ignore any attempts to mark fields. } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { markNoSideEffects(programMethod); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { markNoSideEffects(libraryMethod); } // Small utility methods. private static void markNoSideEffects(Method method) { MethodOptimizationInfo.getMethodOptimizationInfo(method).setNoSideEffects(); } public static boolean hasNoSideEffects(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).hasNoSideEffects(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/NonEmptyStackReturnMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.StackSizeComputer; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This InstructionVisitor marks all methods that return with a non-empty stack * (other than the return value). * * @author Eric Lafortune */ public class NonEmptyStackReturnMarker implements InstructionVisitor { private final StackSizeComputer stackSizeComputer; /** * Creates a new NonEmptyStackReturnMarker * @param stackSizeComputer the stack size computer that can return the * stack sizes at the instructions that are * visited. */ public NonEmptyStackReturnMarker(StackSizeComputer stackSizeComputer) { this.stackSizeComputer = stackSizeComputer; } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { switch (simpleInstruction.opcode) { case Instruction.OP_LRETURN: case Instruction.OP_DRETURN: markReturnWithNonEmptyStack(method, offset, 2); break; case Instruction.OP_IRETURN: case Instruction.OP_FRETURN: case Instruction.OP_ARETURN: markReturnWithNonEmptyStack(method, offset, 1); break; case Instruction.OP_RETURN: markReturnWithNonEmptyStack(method, offset, 0); break; } } // Small utility methods. /** * Marks the given method if the stack before the given instruction offset * has a size larger than the given size. */ private void markReturnWithNonEmptyStack(Method method, int offset, int stackSize) { if (!stackSizeComputer.isReachable(offset) || stackSizeComputer.getStackSizeBefore(offset) > stackSize) { setReturnsWithNonEmptyStack(method); } } private static void setReturnsWithNonEmptyStack(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setReturnsWithNonEmptyStack(); } public static boolean returnsWithNonEmptyStack(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).returnsWithNonEmptyStack(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/NonPrivateMemberMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.*; import proguard.optimize.OptimizationInfoMemberFilter; /** * This ClassVisitor marks all class members that can not be made private in the * classes that it visits, and in the classes to which they refer. * * @author Eric Lafortune */ public class NonPrivateMemberMarker implements ClassVisitor, ConstantVisitor, MemberVisitor { private final MemberVisitor filteredMemberMarker = new OptimizationInfoMemberFilter(this); private final MemberVisitor implementedMethodMarker = new OptimizationInfoMemberFilter( new MethodImplementationFilter(this)); // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Mark all referenced class members in different classes. programClass.constantPoolEntriesAccept(this); // Explicitly mark the method. programClass.methodAccept(ClassConstants.METHOD_NAME_CLINIT, ClassConstants.METHOD_TYPE_CLINIT, filteredMemberMarker); // Explicitly mark the parameterless method. programClass.methodAccept(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, filteredMemberMarker); // Mark all methods that may have implementations. programClass.methodsAccept(implementedMethodMarker); } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // The referenced class member, if any, can never be made private, // even if it's in the same class. stringConstant.referencedMemberAccept(filteredMemberMarker); } public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { Clazz referencedClass = refConstant.referencedClass; // Is it referring to a class member in another class? // The class member might be in another class, or // it may be referenced through another class. if (referencedClass != null && !referencedClass.equals(clazz) || !refConstant.getClassName(clazz).equals(clazz.getName())) { // The referenced class member can never be made private. refConstant.referencedMemberAccept(filteredMemberMarker); } } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { markCanNotBeMadePrivate(programField); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { markCanNotBeMadePrivate(programMethod); } // Small utility methods. private static void markCanNotBeMadePrivate(Field field) { ProgramFieldOptimizationInfo.getProgramFieldOptimizationInfo(field).setCanNotBeMadePrivate(); } /** * Returns whether the given field can be made private. */ public static boolean canBeMadePrivate(Field field) { return FieldOptimizationInfo.getFieldOptimizationInfo(field).canBeMadePrivate(); } private static void markCanNotBeMadePrivate(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setCanNotBeMadePrivate(); } /** * Returns whether the given method can be made private. */ public static boolean canBeMadePrivate(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).canBeMadePrivate(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/OptimizationCodeAttributeFilter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.optimize.KeepMarker; /** * This AttributeVisitor delegates calls for code attributes to another * AttributeVisitor, but only if they can be optimized. *

* Note: any other attribute will not be delegated. *

* * @author Thomas Neidhart */ public class OptimizationCodeAttributeFilter implements AttributeVisitor { private final AttributeVisitor attributeVisitor; private final AttributeVisitor otherAttributeVisitor; /** * Creates a new OptimizationCodeAttributeFilter. * @param attributeVisitor the AttributeVisitor to which visits will * be delegated. */ public OptimizationCodeAttributeFilter(AttributeVisitor attributeVisitor) { this(attributeVisitor, null); } /** * Creates a new OptimizationCodeAttributeFilter. * @param attributeVisitor the AttributeVisitor to which visits will * be delegated if the code attribute can be optimized. * @param otherAttributeVisitor the AttributeVisitor to which visits will * be delegated if the code attribute must be kept. */ public OptimizationCodeAttributeFilter(AttributeVisitor attributeVisitor, AttributeVisitor otherAttributeVisitor) { this.attributeVisitor = attributeVisitor; this.otherAttributeVisitor = otherAttributeVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { AttributeVisitor visitor = !KeepMarker.isKept(codeAttribute) ? attributeVisitor : otherAttributeVisitor; if (visitor != null) { visitor.visitCodeAttribute(clazz, method, codeAttribute); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/PackageVisibleMemberContainingClassMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.*; /** * This ClassVisitor marks all classes that contain package visible members. * * @author Eric Lafortune */ public class PackageVisibleMemberContainingClassMarker implements ClassVisitor, MemberVisitor { // Implementations for ClassVisitor. public void visitAnyClass(Clazz clazz) { // Check the class itself. if ((clazz.getAccessFlags() & AccessConstants.PUBLIC) == 0) { setPackageVisibleMembers(clazz); } else { // Check the members. clazz.fieldsAccept(this); clazz.methodsAccept(this); } } // Implementations for MemberVisitor. public void visitAnyMember(Clazz clazz, Member member) { if ((member.getAccessFlags() & (AccessConstants.PRIVATE | AccessConstants.PUBLIC)) == 0) { setPackageVisibleMembers(clazz); } } // Small utility methods. private static void setPackageVisibleMembers(Clazz clazz) { ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setContainsPackageVisibleMembers(); } public static boolean containsPackageVisibleMembers(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).containsPackageVisibleMembers(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/PackageVisibleMemberInvokingClassMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.*; /** * This ConstantVisitor marks all classes that refer to package visible classes * or class members. * * @author Eric Lafortune */ public class PackageVisibleMemberInvokingClassMarker implements ConstantVisitor, ClassVisitor, MemberVisitor { private Clazz referencingClass; // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Check the referenced class and class member, if any. if (stringConstant.referencedClass != clazz) { referencingClass = clazz; stringConstant.referencedClassAccept(this); stringConstant.referencedMemberAccept(this); } } public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { // Check the referenced class and class member. if (refConstant.referencedClass != clazz) { referencingClass = clazz; refConstant.referencedClassAccept(this); refConstant.referencedMemberAccept(this); } } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Check the referenced class. if (classConstant.referencedClass != clazz) { referencingClass = clazz; classConstant.referencedClassAccept(this); } } // Implementations for ClassVisitor. public void visitAnyClass(Clazz clazz) { if ((clazz.getAccessFlags() & AccessConstants.PUBLIC) == 0) { setInvokesPackageVisibleMembers(referencingClass); } } // Implementations for MemberVisitor. public void visitAnyMember(Clazz clazz, Member member) { if ((member.getAccessFlags() & (AccessConstants.PUBLIC | AccessConstants.PRIVATE)) == 0) { setInvokesPackageVisibleMembers(referencingClass); } } // Small utility methods. private static void setInvokesPackageVisibleMembers(Clazz clazz) { ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setInvokesPackageVisibleMembers(); } public static boolean invokesPackageVisibleMembers(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).invokesPackageVisibleMembers(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ParameterEscapeMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.evaluation.*; import proguard.evaluation.value.*; import proguard.optimize.evaluation.*; /** * This MemberVisitor, AttributeVisitor, and InstructionVisitor marks the * reference parameters that are escaping, that are modified, or that are * returned. * * It also marks methods that may modify anything on the heap. * * The class must be called as a MemberVisitor on all members (to mark the * parameters of native methods, without code attributes), then as an * AttributeVisitor on their code attributes (so it can run its PartialEvaluator * and ReferenceEscapeChecker), and finally as an InstructionVisitor on its * instructions (to actually mark the parameters). * * @see SideEffectClassChecker * @see SideEffectClassMarker * @author Eric Lafortune */ public class ParameterEscapeMarker implements MemberVisitor, AttributeVisitor, InstructionVisitor, ConstantVisitor, ParameterVisitor { private static final Logger logger = LogManager.getLogger(ParameterEscapeMarker.class); private final MemberVisitor extraMemberVisitor; private final PartialEvaluator partialEvaluator; private final boolean runPartialEvaluator; private final ReferenceEscapeChecker referenceEscapeChecker; private final boolean runReferenceEscapeChecker; private final MemberVisitor parameterMarker = new AllParameterVisitor(true, this); // Parameters and values for visitor methods. private Clazz referencingClass; private Method referencingMethod; private int referencingOffset; private int referencingPopCount; private boolean isReturnValueEscaping; private boolean isReturnValueModified; /** * Creates a new ParameterEscapeMarker. */ public ParameterEscapeMarker() { this(null); } /** * Creates a new ParameterEscapeMarker. */ public ParameterEscapeMarker(MemberVisitor extraMemberVisitor) { this(new BasicValueFactory(), extraMemberVisitor ); } /** * Creates a new ParameterEscapeMarker. */ public ParameterEscapeMarker(ValueFactory valueFactory, MemberVisitor extraMemberVisitor) { this(valueFactory, new ReferenceTracingValueFactory(valueFactory), extraMemberVisitor ); } /** * Creates a new ParameterEscapeMarker. */ public ParameterEscapeMarker(ValueFactory valueFactory, ReferenceTracingValueFactory tracingValueFactory, MemberVisitor extraMemberVisitor) { this(new PartialEvaluator(tracingValueFactory, new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)), true, tracingValueFactory), true, extraMemberVisitor ); } /** * Creates a new ParameterEscapeMarker. */ public ParameterEscapeMarker(PartialEvaluator partialEvaluator, boolean runPartialEvaluator, MemberVisitor extraMemberVisitor) { this(partialEvaluator, runPartialEvaluator, new ReferenceEscapeChecker(partialEvaluator, false), true, extraMemberVisitor ); } /** * Creates a new ParameterEscapeMarker. */ public ParameterEscapeMarker(PartialEvaluator partialEvaluator, boolean runPartialEvaluator, ReferenceEscapeChecker referenceEscapeChecker, boolean runReferenceEscapeChecker, MemberVisitor extraMemberVisitor) { this.extraMemberVisitor = extraMemberVisitor; this.partialEvaluator = partialEvaluator; this.runPartialEvaluator = runPartialEvaluator; this.referenceEscapeChecker = referenceEscapeChecker; this.runReferenceEscapeChecker = runReferenceEscapeChecker; } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { int accessFlags = programMethod.getAccessFlags(); // Is it a native method? if ((accessFlags & AccessConstants.NATIVE) != 0) { // Mark all parameters. markModifiedParameters(programClass, programMethod, -1L); markEscapingParameters(programClass, programMethod, -1L); markReturnedParameters(programClass, programMethod, -1L); markAnythingModified(programClass, programMethod); } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Evaluate the code. if (runPartialEvaluator) { partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); } if (runReferenceEscapeChecker) { referenceEscapeChecker.visitCodeAttribute(clazz, method, codeAttribute); } // These results are not complete yet, since this class must still // be called as an InstructionVisitor. logger.debug("ParameterEscapeMarker: [{}.{}{}]", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz) ); int parameterCount = ClassUtil.internalMethodParameterCount(method.getDescriptor(clazz), method.getAccessFlags()); if (logger.getLevel().isLessSpecificThan(Level.DEBUG)) { for (int index = 0; index < parameterCount; index++) { logger.debug(" {}{}{} P{}", // (hasParameterEscaped(method, index) ? 'e' : '.'), isParameterEscaping(method, index) ? 'E' : '.', isParameterReturned(method, index) ? 'R' : '.', isParameterModified(method, index) ? 'M' : '.', index ); } } logger.debug(" {} Return value", returnsExternalValues(method) ? 'X' : '.'); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { switch (simpleInstruction.opcode) { case Instruction.OP_AASTORE: // Mark array parameters whose element is modified. markModifiedParameters(clazz, method, offset, simpleInstruction.stackPopCount(clazz) - 1); // Mark reference values that are put in the array. markEscapingParameters(clazz, method, offset, 0); break; case Instruction.OP_IASTORE: case Instruction.OP_LASTORE: case Instruction.OP_FASTORE: case Instruction.OP_DASTORE: case Instruction.OP_BASTORE: case Instruction.OP_CASTORE: case Instruction.OP_SASTORE: // Mark array parameters whose element is modified. markModifiedParameters(clazz, method, offset, simpleInstruction.stackPopCount(clazz) - 1); break; case Instruction.OP_ARETURN: // Mark returned reference values. markReturnedParameters(clazz, method, offset, 0); break; case Instruction.OP_ATHROW: // Mark the escaping reference values. markEscapingParameters(clazz, method, offset, 0); break; } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_LDC: case Instruction.OP_LDC_W: case Instruction.OP_NEW: case Instruction.OP_ANEWARRAY: case Instruction.OP_MULTIANEWARRAY: case Instruction.OP_GETSTATIC: // Mark possible modifications due to initializers. referencingClass = clazz; referencingMethod = method; referencingOffset = offset; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; case Instruction.OP_PUTSTATIC: // Mark some global modification. markAnythingModified(clazz, method); // Mark reference values that are put in the field. markEscapingParameters(clazz, method, offset, 0); break; case Instruction.OP_GETFIELD: // Mark the owner of the field. The owner sort of escapes when // the field is retrieved. [DGD-1298] (test2181) markEscapingParameters(clazz, method, offset, 0); break; case Instruction.OP_PUTFIELD: // Mark reference parameters whose field is modified. markModifiedParameters(clazz, method, offset, constantInstruction.stackPopCount(clazz) - 1); // Mark reference values that are put in the field. markEscapingParameters(clazz, method, offset, 0); break; case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: case Instruction.OP_INVOKEINTERFACE: case Instruction.OP_INVOKEDYNAMIC: // Mark reference parameters that are modified as parameters // of the invoked method. // Mark reference values that are escaping as parameters // of the invoked method. // Mark escaped reference parameters in the invoked method. referencingClass = clazz; referencingMethod = method; referencingOffset = offset; referencingPopCount = constantInstruction.stackPopCount(clazz); clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { Clazz referencedClass = stringConstant.referencedClass; // If a static initializer may modify anything, so does the referencing // method. if (referencedClass == null || SideEffectClassChecker.mayHaveSideEffects(clazz, referencedClass)) { markAnythingModified(referencingClass, referencingMethod); } } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { Clazz referencedClass = classConstant.referencedClass; // If a static initializer may modify anything, so does the referencing // method. if (referencedClass == null || SideEffectClassChecker.mayHaveSideEffects(clazz, referencedClass)) { markAnythingModified(referencingClass, referencingMethod); } } public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { markAnythingModified(referencingClass, referencingMethod); } public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, this); } public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { Method referencedMethod = (Method)anyMethodrefConstant.referencedMethod; // If the referenced method or a static initializer may modify anything, // so does the referencing method. if (referencedMethod == null || modifiesAnything(referencedMethod) || SideEffectClassChecker.mayHaveSideEffects(clazz, anyMethodrefConstant.referencedClass, referencedMethod)) { markAnythingModified(referencingClass, referencingMethod); } // Do we know the invoked method? if (referencedMethod == null) { // Mark all parameters of the invoking method that are passed to // the invoked method, since they may escape or or be modified // there. for (int parameterOffset = 0; parameterOffset < referencingPopCount; parameterOffset++) { int stackEntryIndex = referencingPopCount - parameterOffset - 1; markEscapingParameters(referencingClass, referencingMethod, referencingOffset, stackEntryIndex); markModifiedParameters(referencingClass, referencingMethod, referencingOffset, stackEntryIndex); } } else { // Remember whether the return value of the method is escaping or // modified later on. isReturnValueEscaping = referenceEscapeChecker.isInstanceEscaping(referencingOffset); isReturnValueModified = referenceEscapeChecker.isInstanceModified(referencingOffset); // Mark parameters of the invoking method that are passed to the // invoked method and escaping or modified there. anyMethodrefConstant.referencedMethodAccept(parameterMarker); } } // Implementations for ParameterVisitor. public void visitParameter(Clazz clazz, Member member, int parameterIndex, int parameterCount, int parameterOffset, int parameterSize, String parameterType, Clazz referencedClass) { if (!ClassUtil.isInternalPrimitiveType(parameterType.charAt(0))) { Method method = (Method)member; // Is the parameter escaping from the method, // or is it returned and then escaping? if (isParameterEscaping(method, parameterIndex) || (isParameterReturned(method, parameterIndex) && isReturnValueEscaping)) { markEscapingParameters(referencingClass, referencingMethod, referencingOffset, parameterSize - parameterOffset - 1); } // Is the parameter being modified in the method. // or is it returned and then modified? if (isParameterModified(method, parameterIndex) || (isParameterReturned(method, parameterIndex) && isReturnValueModified)) { markModifiedParameters(referencingClass, referencingMethod, referencingOffset, parameterSize - parameterOffset - 1); } } } // Small utility methods. private void reportSideEffect(Clazz clazz, Method method) { if (extraMemberVisitor != null) { method.accept(clazz, extraMemberVisitor); } } /** * Marks the producing reference parameters (and the classes) of the * specified stack entry at the given instruction offset. */ private void markEscapingParameters(Clazz clazz, Method method, int consumerOffset, int stackEntryIndex) { TracedStack stackBefore = partialEvaluator.getStackBefore(consumerOffset); Value stackEntry = stackBefore.getTop(stackEntryIndex); if (stackEntry.computationalType() == Value.TYPE_REFERENCE) { ReferenceValue referenceValue = stackEntry.referenceValue(); // The null reference value may not have a trace value. if (referenceValue.isNull() != Value.ALWAYS) { markEscapingParameters(clazz, method, referenceValue); } } } /** * Marks the producing parameters (and the classes) of the given * reference value. */ private void markEscapingParameters(Clazz clazz, Method method, ReferenceValue referenceValue) { TracedReferenceValue tracedReferenceValue = (TracedReferenceValue)referenceValue; InstructionOffsetValue producers = tracedReferenceValue.getTraceValue().instructionOffsetValue(); int producerCount = producers.instructionOffsetCount(); for (int index = 0; index < producerCount; index++) { if (producers.isMethodParameter(index)) { // We know exactly which parameter is escaping. markParameterEscaping(clazz, method, producers.methodParameter(index)); } } } /** * Marks the given parameter as escaping from the given method. */ private void markParameterEscaping(Clazz clazz, Method method, int parameterIndex) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(method); if (!methodOptimizationInfo.isParameterEscaping(parameterIndex) && methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setParameterEscaping(parameterIndex); // Trigger the repeater if the setter has changed the value. if (methodOptimizationInfo.isParameterEscaping(parameterIndex)) { reportSideEffect(clazz, method); } } } /** * Marks the given parameters as escaping from the given method. */ private void markEscapingParameters(Clazz clazz, Method method, long escapingParameters) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(method); long oldEscapingParameters = methodOptimizationInfo.getEscapingParameters(); if ((~oldEscapingParameters & escapingParameters) != 0 && methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ((ProgramMethodOptimizationInfo)methodOptimizationInfo).updateEscapingParameters(escapingParameters); // Trigger the repeater if the setter has changed the value. if (methodOptimizationInfo.getEscapingParameters() != oldEscapingParameters) { reportSideEffect(clazz, method); } } } /** * Returns whether the given parameter is escaping from the given method. */ public static boolean isParameterEscaping(Method method, int parameterIndex) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).isParameterEscaping(parameterIndex); } /** * Returns which parameters are escaping from the given method. */ public static long getEscapingParameters(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).getEscapingParameters(); } /** * Marks the method and the returned reference parameters of the specified * stack entry at the given instruction offset. */ private void markReturnedParameters(Clazz clazz, Method method, int returnOffset, int stackEntryIndex) { TracedStack stackBefore = partialEvaluator.getStackBefore(returnOffset); Value stackEntry = stackBefore.getTop(stackEntryIndex); if (stackEntry.computationalType() == Value.TYPE_REFERENCE) { ReferenceValue referenceValue = stackEntry.referenceValue(); // The null reference value may not have a trace value. if (referenceValue.isNull() != Value.ALWAYS && mayReturnType(clazz, method, referenceValue)) { markReturnedParameters(clazz, method, referenceValue); } } } /** * Marks the method and the producing parameters of the given reference * value. */ private void markReturnedParameters(Clazz clazz, Method method, ReferenceValue referenceValue) { TracedReferenceValue tracedReferenceValue = (TracedReferenceValue)referenceValue; InstructionOffsetValue producers = tracedReferenceValue.getTraceValue().instructionOffsetValue(); int producerCount = producers.instructionOffsetCount(); for (int index = 0; index < producerCount; index++) { if (producers.isMethodParameter(index)) { // We know exactly which parameter is returned. markParameterReturned(clazz, method, producers.methodParameter(index)); } else if (producers.isFieldValue(index)) { markReturnsExternalValues(clazz, method); } else if (producers.isNewinstance(index) || producers.isExceptionHandler(index)) { markReturnsNewInstances(clazz, method); } } } /** * Marks the given parameter as returned from the given method. */ private void markParameterReturned(Clazz clazz, Method method, int parameterIndex) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(method); if (!methodOptimizationInfo.returnsParameter(parameterIndex) && methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setParameterReturned(parameterIndex); // Trigger the repeater if the setter has changed the value. if (methodOptimizationInfo.returnsParameter(parameterIndex)) { reportSideEffect(clazz, method); } } } /** * Marks the given parameters as returned from the given method. */ private void markReturnedParameters(Clazz clazz, Method method, long returnedParameters) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(method); long oldReturnedParameters = methodOptimizationInfo.getReturnedParameters(); if ((~oldReturnedParameters & returnedParameters) != 0 && methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ((ProgramMethodOptimizationInfo)methodOptimizationInfo).updateReturnedParameters(returnedParameters); // Trigger the repeater if the setter has changed the value. if (methodOptimizationInfo.getReturnedParameters() != oldReturnedParameters) { reportSideEffect(clazz, method); } } } /** * Returns whether the given parameter is returned from the given method. */ public static boolean isParameterReturned(Method method, int parameterIndex) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).returnsParameter(parameterIndex); } /** * Returns which parameters are returned from the given method. */ public static long getReturnedParameters(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).getReturnedParameters(); } /** * Marks that the given method returns new instances (created inside the * method). */ private void markReturnsNewInstances(Clazz clazz, Method method) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(method); if (!methodOptimizationInfo.returnsNewInstances() && methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setReturnsNewInstances(); // Trigger the repeater if the setter has changed the value. if (methodOptimizationInfo.returnsNewInstances()) { reportSideEffect(clazz, method); } } } /** * Returns whether the given method returns new instances (created inside * the method). */ public static boolean returnsNewInstances(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).returnsNewInstances(); } /** * Marks that the given method returns external reference values (not * parameter or new instance). */ private void markReturnsExternalValues(Clazz clazz, Method method) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(method); if (!methodOptimizationInfo.returnsExternalValues() && methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setReturnsExternalValues(); // Trigger the repeater if the setter has changed the value. if (methodOptimizationInfo.returnsExternalValues()) { reportSideEffect(clazz, method); } } } /** * Returns whether the given method returns external reference values * (not parameter or new instance). */ public static boolean returnsExternalValues(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).returnsExternalValues(); } /** * Returns whether the given method may return the given type of reference * value */ private boolean mayReturnType(Clazz clazz, Method method, ReferenceValue referenceValue) { String returnType = ClassUtil.internalMethodReturnType(method.getDescriptor(clazz)); Clazz[] referencedClasses = method instanceof ProgramMethod ? ((ProgramMethod)method).referencedClasses : ((LibraryMethod)method).referencedClasses; Clazz referencedClass = referencedClasses == null || !ClassUtil.isInternalClassType(returnType) ? null : referencedClasses[referencedClasses.length - 1]; return referenceValue.instanceOf(returnType, referencedClass) != Value.NEVER; } /** * Marks the producing reference parameters of the specified stack entry at * the given instruction offset. */ private void markModifiedParameters(Clazz clazz, Method method, int offset, int stackEntryIndex) { TracedStack stackBefore = partialEvaluator.getStackBefore(offset); Value stackEntry = stackBefore.getTop(stackEntryIndex); if (stackEntry.computationalType() == Value.TYPE_REFERENCE) { ReferenceValue referenceValue = stackEntry.referenceValue(); // The null reference value may not have a trace value. if (referenceValue.isNull() != Value.ALWAYS) { markModifiedParameters(clazz, method, referenceValue); } } } /** * Marks the producing parameters of the given reference value. */ private void markModifiedParameters(Clazz clazz, Method method, ReferenceValue referenceValue) { TracedReferenceValue tracedReferenceValue = (TracedReferenceValue)referenceValue; InstructionOffsetValue producers = tracedReferenceValue.getTraceValue().instructionOffsetValue(); int producerCount = producers.instructionOffsetCount(); for (int index = 0; index < producerCount; index++) { if (producers.isMethodParameter(index)) { // We know exactly which parameter is being modified. markParameterModified(clazz, method, producers.methodParameter(index)); } else if (!producers.isNewinstance(index) && !producers.isExceptionHandler(index)) { // If some unknown instance is modified, any escaping parameters // may be modified. markModifiedParameters(clazz, method, getEscapingParameters(method)); markAnythingModified(clazz, method); } } } /** * Marks the given parameter as modified by the given method. */ private void markParameterModified(Clazz clazz, Method method, int parameterIndex) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(method); if (!methodOptimizationInfo.isParameterModified(parameterIndex) && methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setParameterModified(parameterIndex); // Trigger the repeater if the setter has changed the value. if (methodOptimizationInfo.isParameterModified(parameterIndex)) { reportSideEffect(clazz, method); } } } /** * Marks the given parameters as modified by the given method. */ private void markModifiedParameters(Clazz clazz, Method method, long modifiedParameters) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(method); long oldModifiedParameters = methodOptimizationInfo.getModifiedParameters(); if ((~oldModifiedParameters & modifiedParameters) != 0 && methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ((ProgramMethodOptimizationInfo)methodOptimizationInfo).updateModifiedParameters(modifiedParameters); // Trigger the repeater if the setter has changed the value. if (methodOptimizationInfo.getModifiedParameters() != oldModifiedParameters) { reportSideEffect(clazz, method); } } } /** * Returns whether the given parameter is modified by the given method. */ public static boolean isParameterModified(Method method, int parameterIndex) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).isParameterModified(parameterIndex); } /** * Returns which parameters are modified by the given method. */ public static long getModifiedParameters(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).getModifiedParameters(); } /** * Marks that anything may be modified by the given method. */ private void markAnythingModified(Clazz clazz, Method method) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(method); if (!methodOptimizationInfo.modifiesAnything() && methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setModifiesAnything(); // Trigger the repeater if the setter has changed the value. if (methodOptimizationInfo.modifiesAnything()) { reportSideEffect(clazz, method); } } } /** * Returns whether anything may be modified by the given method. This takes * into account the side effects of static initializers, except the static * initializer of the invoked method (because it is better checked * explicitly as a function of the referencing class). * * @see SideEffectClassChecker#mayHaveSideEffects(Clazz, Clazz, Member) */ public static boolean modifiesAnything(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).modifiesAnything(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ParameterEscapedMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.evaluation.*; import proguard.evaluation.value.*; import proguard.optimize.evaluation.*; /** * This ClassPoolVisitor marks the reference parameters that have escaped or * that are escaping, outside or inside their methods. * * @see ReferenceEscapeChecker * @see ParameterEscapeMarker * @author Eric Lafortune */ public class ParameterEscapedMarker implements ClassPoolVisitor, MemberVisitor, AttributeVisitor, InstructionVisitor, ConstantVisitor { private static final Logger logger = LogManager.getLogger(ParameterEscapedMarker.class); private final ClassVisitor parameterEscapedMarker = new AllMethodVisitor( new AllAttributeVisitor(this)); private final ValueFactory valueFactory = new BasicValueFactory(); private final ReferenceTracingValueFactory tracingValueFactory = new ReferenceTracingValueFactory(valueFactory); private final PartialEvaluator partialEvaluator = new PartialEvaluator(tracingValueFactory, new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)), true, tracingValueFactory); private final ReferenceEscapeChecker referenceEscapeChecker = new ReferenceEscapeChecker(partialEvaluator, false); // Parameters and values for visitor methods. private boolean newEscapes; private Method referencingMethod; private int referencingOffset; private int referencingPopCount; /** * Creates a new ParameterModificationMarker. */ public ParameterEscapedMarker() { } // Implementations for ClassPoolVisitor. public void visitClassPool(ClassPool classPool) { // Go over all classes and their methods, marking if parameters are // modified, until no new cases can be found. do { newEscapes = false; logger.debug("ParameterEscapedMarker: new iteration"); // Go over all classes and their methods once. classPool.classesAccept(parameterEscapedMarker); } while (newEscapes); if (logger.getLevel().isLessSpecificThan(Level.DEBUG)) { classPool.classesAccept(new AllMethodVisitor(this)); } } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { logger.debug("ParameterEscapedMarker: [{}.{}{}]", programClass.getName(), programMethod.getName(programClass), programMethod.getDescriptor(programClass) ); int parameterSize = ClassUtil.internalMethodParameterSize(programMethod.getDescriptor(programClass), programMethod.getAccessFlags()); if (logger.getLevel().isLessSpecificThan(Level.DEBUG)) { for (int index = 0; index < parameterSize; index++) { logger.debug(" {} P{}", hasParameterEscaped(programMethod, index) ? 'e' : '.', index ); } } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Evaluate the code. partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); referenceEscapeChecker.visitCodeAttribute(clazz, method, codeAttribute); // Mark the parameters that are modified from the code. codeAttribute.instructionsAccept(clazz, method, partialEvaluator.tracedInstructionFilter(this)); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: case Instruction.OP_INVOKEINTERFACE: // Mark escaped reference parameters in the invoked method. referencingMethod = method; referencingOffset = offset; referencingPopCount = constantInstruction.stackPopCount(clazz); clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { Method referencedMethod = (Method)anyMethodrefConstant.referencedMethod; if (referencedMethod != null && MethodOptimizationInfo.getMethodOptimizationInfo(referencedMethod) instanceof ProgramMethodOptimizationInfo) { // Mark reference parameters that are passed to the method. for (int parameterIndex = 0; parameterIndex < referencingPopCount; parameterIndex++) { int stackEntryIndex = referencingPopCount - parameterIndex - 1; TracedStack stackBefore = partialEvaluator.getStackBefore(referencingOffset); Value stackEntry = stackBefore.getTop(stackEntryIndex); if (stackEntry.computationalType() == Value.TYPE_REFERENCE) { // Has the parameter escaped outside or inside the referencing // method? if (hasEscapedBefore(referencingOffset, stackEntryIndex)) { markParameterEscaped(referencedMethod, parameterIndex); } } } } } // Small utility methods. /** * Returns whether any of the producing reference values of the specified * stack entry before the given instruction offset are escaping or have * escaped. */ private boolean hasEscapedBefore(int instructionOffset, int stackEntryIndex) { TracedStack stackBefore = partialEvaluator.getStackBefore(instructionOffset); Value stackEntry = stackBefore.getTop(stackEntryIndex); if (stackEntry.computationalType() == Value.TYPE_REFERENCE) { ReferenceValue referenceValue = stackEntry.referenceValue(); // The null reference value may not have a trace value. if (referenceValue.isNull() != Value.ALWAYS && hasEscaped(referenceValue)) { return true; } } return false; } /** * Returns whether the producing reference value is escaping or has escaped. */ private boolean hasEscaped(ReferenceValue referenceValue) { TracedReferenceValue tracedReferenceValue = (TracedReferenceValue)referenceValue; InstructionOffsetValue instructionOffsetValue = tracedReferenceValue.getTraceValue().instructionOffsetValue(); int count = instructionOffsetValue.instructionOffsetCount(); for (int index = 0; index < count; index++) { if (instructionOffsetValue.isMethodParameter(index) ? hasParameterEscaped(referencingMethod, instructionOffsetValue.methodParameter(index)) : referenceEscapeChecker.isInstanceEscaping(instructionOffsetValue.instructionOffset(index))) { return true; } } return false; } /** * Marks the given parameter as escaped from the given method. */ private void markParameterEscaped(Method method, int parameterIndex) { ProgramMethodOptimizationInfo info = ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method); if (!info.hasParameterEscaped(parameterIndex)) { info.setParameterEscaped(parameterIndex); newEscapes = true; } } /** * Marks the given parameters as escaped from the given method. */ private void markEscapedParameters(Method method, long escapedParameters) { ProgramMethodOptimizationInfo info = ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method); if ((~info.getEscapedParameters() & escapedParameters) != 0) { info.updateEscapedParameters(escapedParameters); newEscapes = true; } } /** * Returns whether the given parameter is escaped from the given method. */ public static boolean hasParameterEscaped(Method method, int parameterIndex) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).hasParameterEscaped(parameterIndex); } /** * Returns which parameters are escaped from the given method. */ public static long getEscapedParameters(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).getEscapedParameters(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ParameterUsageMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.value.*; /** * This MemberVisitor counts the parameters and marks the used parameters * of the methods that it visits. It also marks the 'this' parameters of * methods that have hierarchies. * * @author Eric Lafortune */ public class ParameterUsageMarker implements MemberVisitor, AttributeVisitor, InstructionVisitor { private static final Logger logger = LogManager.getLogger(ParameterUsageMarker.class); private final boolean markThisParameter; private final boolean markAllParameters; private final boolean analyzeCode; private final PartialEvaluator partialEvaluator = new PartialEvaluator(); /** * Creates a new ParameterUsageMarker. */ public ParameterUsageMarker() { this(false, false); } /** * Creates a new ParameterUsageMarker that optionally marks all parameters. * @param markThisParameter specifies whether all 'this' parameters should * be marked as being used. * @param markAllParameters specifies whether all other parameters should * be marked as being used. */ public ParameterUsageMarker(boolean markThisParameter, boolean markAllParameters) { this(markThisParameter, markAllParameters, true); } /** * Creates a new ParameterUsageMarker that optionally marks all parameters. * @param markThisParameter specifies whether all 'this' parameters should * be marked as being used. * @param markAllParameters specifies whether all other parameters should * be marked as being used. * @param analyzeCode specifies whether the code of visited methods * should be analyzed for used parameters. */ public ParameterUsageMarker(boolean markThisParameter, boolean markAllParameters, boolean analyzeCode) { this.markThisParameter = markThisParameter; this.markAllParameters = markAllParameters; this.analyzeCode = analyzeCode; } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { int parameterSize = ClassUtil.internalMethodParameterSize(programMethod.getDescriptor(programClass), programMethod.getAccessFlags()); if (parameterSize > 0) { int accessFlags = programMethod.getAccessFlags(); // Must we mark the 'this' parameter? if (markThisParameter && (accessFlags & AccessConstants.STATIC) == 0) { // Mark the 'this' parameter. markParameterUsed(programMethod, 0); } // Must we mark all other parameters? if (markAllParameters) { // Mark all parameters, without the 'this' parameter. markUsedParameters(programMethod, (accessFlags & AccessConstants.STATIC) != 0 ? -1L : -2L); } // Is it a native method? if ((accessFlags & AccessConstants.NATIVE) != 0) { // Mark all parameters. markUsedParameters(programMethod, -1L); } // Is it an abstract method? else if ((accessFlags & AccessConstants.ABSTRACT) != 0) { // Mark the 'this' parameter. markParameterUsed(programMethod, 0); } // Is it a non-native, concrete method? else { // Is the method not static, but synchronized, or can it have // other implementations, or is it a class instance initializer? if ((accessFlags & AccessConstants.STATIC) == 0 && ((accessFlags & AccessConstants.SYNCHRONIZED) != 0 || programClass.mayHaveImplementations(programMethod) || programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_INIT))) { // Mark the 'this' parameter. markParameterUsed(programMethod, 0); } if (analyzeCode) { // Mark the parameters that are used by the code. programMethod.attributesAccept(programClass, this); } } logger.debug("{}", () -> { StringBuilder debugMessage = new StringBuilder(String.format("ParameterUsageMarker: [%s.%s%s]: ", programClass.getName(), programMethod.getName(programClass), programMethod.getDescriptor(programClass))); for (int variableIndex = 0; variableIndex < parameterSize; variableIndex++) { debugMessage.append(isParameterUsed(programMethod, variableIndex) ? '+' : '-'); } return debugMessage.toString(); } ); } // Set the parameter size. setParameterSize(programMethod, parameterSize); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { // Can the method have other implementations? if (libraryClass.mayHaveImplementations(libraryMethod)) { // All implementations must keep all parameters of this method, // including the 'this' parameter. markUsedParameters(libraryMethod, -1L); } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Evaluate the code. partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); // Mark the parameters that are used by the code. codeAttribute.instructionsAccept(clazz, method, this); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { if (partialEvaluator.isTraced(offset) && variableInstruction.isLoad()) { int variableIndex = variableInstruction.variableIndex; if (variableIndex < codeAttribute.u2maxLocals) { // The parameter indices stored in the producer values are // parameter offsets, taking into account Category 2 types, // and therefore compatible with variable indices. Value producer = partialEvaluator.getVariablesBefore(offset).getProducerValue(variableIndex); if (producer != null && producer.instructionOffsetValue().contains(variableIndex | InstructionOffsetValue.METHOD_PARAMETER)) { // Mark the variable. markParameterUsed(method, variableIndex); // Account for Category 2 instructions, which take up two entries. if (variableInstruction.stackPopCount(clazz) == 2 || variableInstruction.stackPushCount(clazz) == 2) { markParameterUsed(method, variableIndex + 1); } } } } } // Small utility methods. /** * Sets the total size of the parameters. */ private static void setParameterSize(Method method, int parameterSize) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setParameterSize(parameterSize); } /** * Returns the total size of the parameters. */ public static int getParameterSize(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).getParameterSize(); } /** * Marks the given parameter as being used. */ public static void markParameterUsed(Method method, int variableIndex) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setParameterUsed(variableIndex); } /** * Marks the given parameters as being used. */ private static void markUsedParameters(Method method, long usedParameters) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).updateUsedParameters(usedParameters); } /** * Returns whether the given method has any unused parameters. */ public static boolean hasUnusedParameters(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).hasUnusedParameters(); } /** * Returns whether the given parameter is being used. */ public static boolean isParameterUsed(Method method, int variableIndex) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).isParameterUsed(variableIndex); } /** * Returns which parameters are being used. */ public static long getUsedParameters(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).getUsedParameters(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ProgramClassOptimizationInfo.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.Clazz; /** * This class stores some optimization information that can be attached to * a class that can be analyzed in detail. * * @author Eric Lafortune */ public class ProgramClassOptimizationInfo extends ClassOptimizationInfo { private volatile boolean containsConstructors = false; private volatile boolean isInstantiated = false; private volatile boolean isInstanceofed = false; private volatile boolean isDotClassed = false; private volatile boolean isCaught = false; private volatile boolean isSimpleEnum = false; private volatile boolean isEscaping = false; private volatile boolean hasSideEffects = false; private volatile boolean containsPackageVisibleMembers = false; private volatile boolean invokesPackageVisibleMembers = false; private volatile boolean mayBeMerged = true; private volatile Clazz wrappedClass; private volatile Clazz targetClass; public boolean isKept() { return false; } public void setContainsConstructors() { containsConstructors = true; } public boolean containsConstructors() { return containsConstructors; } /** * Specifies that the class is instantiated in the known code. */ public void setInstantiated() { isInstantiated = true; } public boolean isInstantiated() { return isInstantiated; } /** * Specifies that the class is part of an 'instanceof' instruction in the * known code. */ public void setInstanceofed() { isInstanceofed = true; } public boolean isInstanceofed() { return isInstanceofed; } /** * Specifies that the class is loaded with an 'ldc' instruction (a .class * construct in Java) in the known code. */ public void setDotClassed() { isDotClassed = true; } public boolean isDotClassed() { return isDotClassed; } /** * Specifies that the class is a Throwable that is caught in an exception * handler in the known code. */ public void setCaught() { isCaught = true; } public boolean isCaught() { return isCaught; } /** * Specifies whether the class is an enum type that can be simplified to a * primitive integer. */ public void setSimpleEnum(boolean simple) { isSimpleEnum = simple; } public boolean isSimpleEnum() { return isSimpleEnum; } /** * Specifies that instances of the class are escaping to the heap. * Otherwise, any instances are just created locally and passed as * parameters. */ public void setEscaping() { isEscaping = true; } public boolean isEscaping() { return isEscaping; } /** * Specifies that loading the class has side effects. */ public void setSideEffects() { hasSideEffects = true; } public boolean hasSideEffects() { return !hasNoSideEffects && hasSideEffects; } /** * Specifies that the class contains package visible class members. */ public void setContainsPackageVisibleMembers() { containsPackageVisibleMembers = true; } public boolean containsPackageVisibleMembers() { return containsPackageVisibleMembers; } /** * Specifies that code in the class accesses package visible class members. */ public void setInvokesPackageVisibleMembers() { invokesPackageVisibleMembers = true; } public boolean invokesPackageVisibleMembers() { return invokesPackageVisibleMembers; } /** * Specifies that the class may be not merged with other classes. */ public void setMayNotBeMerged() { mayBeMerged = false; } public boolean mayBeMerged() { return mayBeMerged; } /** * Specifies the class for which this class is a simple wrapper without any * additional functionality. */ public void setWrappedClass(Clazz wrappedClass) { this.wrappedClass = wrappedClass; } public Clazz getWrappedClass() { return wrappedClass; } /** * Specifies the class into which this class can be merged. */ public void setTargetClass(Clazz targetClass) { this.targetClass = targetClass; } public Clazz getTargetClass() { return targetClass; } /** * Merges in the given information of a class that is merged. */ public void merge(ClassOptimizationInfo other) { this.isInstantiated |= other.isInstantiated(); this.isInstanceofed |= other.isInstanceofed(); this.isDotClassed |= other.isDotClassed(); this.isCaught |= other.isCaught(); this.isSimpleEnum |= other.isSimpleEnum(); this.isEscaping |= other.isEscaping(); this.hasSideEffects |= other.hasSideEffects(); this.containsPackageVisibleMembers |= other.containsPackageVisibleMembers(); this.invokesPackageVisibleMembers |= other.invokesPackageVisibleMembers(); this.containsConstructors |= other.containsConstructors(); } /** * Creates and sets a ProgramClassOptimizationInfo instance on the given class. */ public static void setProgramClassOptimizationInfo(Clazz clazz) { clazz.setProcessingInfo(new ProgramClassOptimizationInfo()); } /** * Returns the ProgramClassOptimizationInfo instance from the given class. */ public static ProgramClassOptimizationInfo getProgramClassOptimizationInfo(Clazz clazz) { return (ProgramClassOptimizationInfo)clazz.getProcessingInfo(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ProgramClassOptimizationInfoSetter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor attaches a ProgramClassOptimizationInfo instance to every * class that is not being kept that it visits. * * @author Eric Lafortune */ public class ProgramClassOptimizationInfoSetter implements ClassVisitor { private final boolean overwrite; /** * Creates a new ProgramClassOptimizationInfoSetter. * Existing processing info is not overridden. */ public ProgramClassOptimizationInfoSetter() { this(false); } /** * Creates a new ProgramClassOptimizationInfoSetter. * * @param overwrite true if existing processing info should be overridden, * false otherwise. */ public ProgramClassOptimizationInfoSetter(boolean overwrite) { this.overwrite = overwrite; } // Implementations for MemberVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { if (programClass.getProcessingInfo() == null || overwrite) { ProgramClassOptimizationInfo.setProgramClassOptimizationInfo(programClass); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ProgramFieldOptimizationInfo.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.util.ClassUtil; import proguard.evaluation.ConstantValueFactory; import proguard.evaluation.value.*; /** * This class stores some optimization information that can be attached to * a field that can be analyzed in detail. * * @author Eric Lafortune */ public class ProgramFieldOptimizationInfo extends FieldOptimizationInfo implements AttributeVisitor { private static final ValueFactory VALUE_FACTORY = new ParticularValueFactory(); private static final ConstantValueFactory CONSTANT_VALUE_FACTORY = new ConstantValueFactory(VALUE_FACTORY); private static final InitialValueFactory INITIAL_VALUE_FACTORY = new InitialValueFactory(VALUE_FACTORY); private volatile boolean isWritten; private volatile boolean isRead; private volatile boolean canBeMadePrivate = true; private final boolean alwaysInitializeValue; private volatile ReferenceValue referencedClass; public ProgramFieldOptimizationInfo(Clazz clazz, Field field, boolean alwaysInitializeValue) { int accessFlags = field.getAccessFlags(); isWritten = isRead = (accessFlags & AccessConstants.VOLATILE) != 0; this.alwaysInitializeValue = alwaysInitializeValue; resetValue(clazz, field); } public ProgramFieldOptimizationInfo(ProgramFieldOptimizationInfo programFieldOptimizationInfo) { this.value = programFieldOptimizationInfo.value; this.isWritten = programFieldOptimizationInfo.isWritten; this.isRead = programFieldOptimizationInfo.isRead; this.canBeMadePrivate = programFieldOptimizationInfo.canBeMadePrivate; this.alwaysInitializeValue = programFieldOptimizationInfo.alwaysInitializeValue; this.referencedClass = programFieldOptimizationInfo.referencedClass; } // Overridden methods for FieldOptimizationInfo, plus more setters. public boolean isKept() { return false; } /** * Specifies that the field is written to. */ public void setWritten() { isWritten = true; } public boolean isWritten() { return isWritten; } /** * Specifies that the field is read. */ public void setRead() { isRead = true; } public boolean isRead() { return isRead; } /** * Specifies that the field can be made private. */ public void setCanNotBeMadePrivate() { canBeMadePrivate = false; } public boolean canBeMadePrivate() { return canBeMadePrivate; } /** * Specifies a representation of the class through which the field is * accessed. */ public synchronized void generalizeReferencedClass(ReferenceValue referencedClass) { this.referencedClass = this.referencedClass != null ? this.referencedClass.generalize(referencedClass) : referencedClass; } public ReferenceValue getReferencedClass() { return referencedClass; } /** * Initializes the representation of the value of the field. */ public void resetValue(Clazz clazz, Field field) { int accessFlags = field.getAccessFlags(); value = null; // See if we can initialize the static field with a constant value. if ((accessFlags & AccessConstants.STATIC) != 0) { field.accept(clazz, new AllAttributeVisitor(this)); } // Otherwise initialize a non-final field with the default value. // Conservatively, even a final field needs to be initialized with the // default value, because it may be accessed before it is set. if (value == null && (alwaysInitializeValue || // Final reference fields can not be inlined by the compiler and thus // can be null in certain cases. !ClassUtil.isInternalPrimitiveType(field.getDescriptor(clazz)) || (accessFlags & AccessConstants.FINAL) == 0)) { // Otherwise initialize the non-final field with the default value. value = INITIAL_VALUE_FACTORY.createValue(field.getDescriptor(clazz)); } } /** * Specifies a representation of the value of the field. */ public synchronized void generalizeValue(Value value) { this.value = this.value != null ? this.value.generalize(value) : value; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute) { // Retrieve the initial static field value. value = CONSTANT_VALUE_FACTORY.constantValue(clazz, constantValueAttribute.u2constantValueIndex); } // Small utility methods. /** * Creates and sets a ProgramFieldOptimizationInfo instance on the given field. */ public static void setProgramFieldOptimizationInfo(Clazz clazz, Field field, boolean optimizeConservatively) { field.setProcessingInfo(new ProgramFieldOptimizationInfo(clazz, field, optimizeConservatively)); } /** * Returns the ProgramFieldOptimizationInfo instance from the given field. */ public static ProgramFieldOptimizationInfo getProgramFieldOptimizationInfo(Field field) { return (ProgramFieldOptimizationInfo)field.getProcessingInfo(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ProgramMemberOptimizationInfoSetter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor attaches a ProgramFieldOptimizationInfo instance to every * field and a ProgramMethodOptimizationInfo instance to every method that is * not being kept that it visits. * * @author Eric Lafortune */ public class ProgramMemberOptimizationInfoSetter implements MemberVisitor { private final boolean overwrite; private final boolean optimizeConservatively; /** * Creates a new ProgramMemberOptimizationInfoSetter that only attaches a * ProgramFieldOptimizationInfo to a member if no other info is present * on the member yet, and does not apply conservative optimization */ public ProgramMemberOptimizationInfoSetter() { this(false); } /** * Creates a new ProgramMemberOptimizationInfoSetter that does not * apply conservative optimization. * * @param overwrite boolean indicating whether an existing processing info on * a visited member should be overwritten or not. */ public ProgramMemberOptimizationInfoSetter(boolean overwrite) { this(overwrite, false); } /** * Creates a new ProgramMemberOptimizationInfoSetter * @param overwrite boolean indicating whether an existing * processing info on a visited member should * be overwritten or not. * @param optimizeConservatively boolean indicating whether conservative * optimization should be applied */ public ProgramMemberOptimizationInfoSetter(boolean overwrite, boolean optimizeConservatively) { this.overwrite = overwrite; this.optimizeConservatively = optimizeConservatively; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { if (programField.getProcessingInfo() == null || overwrite) { ProgramFieldOptimizationInfo.setProgramFieldOptimizationInfo(programClass, programField, optimizeConservatively); } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (MethodLinker.lastMember(programMethod).getProcessingInfo() == null || overwrite) { ProgramMethodOptimizationInfo.setProgramMethodOptimizationInfo(programClass, programMethod); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ProgramMethodOptimizationInfo.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.evaluation.value.Value; import proguard.util.ArrayUtil; /** * This class stores some optimization information that can be attached to * a method that can be analyzed in detail. * * @author Eric Lafortune */ public class ProgramMethodOptimizationInfo extends MethodOptimizationInfo { private static final Value[] EMPTY_PARAMETERS = new Value[0]; private static final int[] EMPTY_PARAMETER_SIZES = new int[0]; private volatile boolean hasSideEffects = false; private volatile boolean canBeMadePrivate = true; private volatile boolean catchesExceptions = false; private volatile boolean branchesBackward = false; private volatile boolean invokesSuperMethods = false; private volatile boolean invokesDynamically = false; private volatile boolean accessesPrivateCode = false; private volatile boolean accessesPackageCode = false; private volatile boolean accessesProtectedCode = false; private volatile boolean hasSynchronizedBlock = false; private volatile boolean assignsFinalField = false; private volatile boolean returnsWithNonEmptyStack = false; private volatile int invocationCount = 0; private volatile int parameterSize = 0; private volatile long usedParameters = 0L; private volatile long escapedParameters = 0L; private volatile long escapingParameters = 0L; private volatile long modifiedParameters = 0L; private volatile boolean modifiesAnything = false; private volatile Value[] parameters; private volatile int[] parameterSizes; private volatile long returnedParameters = 0L; private volatile boolean returnsNewInstances = false; private volatile boolean returnsExternalValues = false; /** * Creates a new MethodOptimizationInfo for the given method. */ public ProgramMethodOptimizationInfo(Clazz clazz, Method method) { // Set up an array of the right size for storing information about the // passed parameters (including 'this', for non-static methods) and // their respective stack sizes. String desc = method.getDescriptor(clazz); boolean isStatic = (method.getAccessFlags() & AccessConstants.STATIC) != 0; int parameterCount = ClassUtil.internalMethodParameterCount(desc, isStatic); parameters = parameterCount == 0 ? EMPTY_PARAMETERS : new Value[parameterCount]; parameterSizes = parameterCount == 0 ? EMPTY_PARAMETER_SIZES : new int[parameterCount]; InternalTypeEnumeration typeEnumeration = new InternalTypeEnumeration(desc); int idx = isStatic ? 0 : 1; // Pre-initialize the size of the first parameter // if the method is non-static. if (parameterSizes.length > 0 && !isStatic) { parameterSizes[0] = 1; } while (typeEnumeration.hasMoreTypes()) { String internalType = typeEnumeration.nextType(); parameterSizes[idx++] = ClassUtil.internalTypeSize(internalType); } } // Overridden methods for MethodOptimizationInfo, plus more setters. public boolean isKept() { return false; } /** * Specifies that the method has side effects. Side effects include * changing static fields, changing instance fields, changing objects on * the heap, calling other methods with side effects, etc. * * The method {@link #setNoSideEffects()} gets precedence. */ public void setSideEffects() { hasSideEffects = true; } public boolean hasSideEffects() { return !hasNoSideEffects && hasSideEffects; } /** * Specifies that the method can't be made private. */ public void setCanNotBeMadePrivate() { canBeMadePrivate = false; } public boolean canBeMadePrivate() { return canBeMadePrivate; } /** * Specifies that the method body contains exception handlers. */ public void setCatchesExceptions() { catchesExceptions = true; } public boolean catchesExceptions() { return catchesExceptions; } /** * Specifies that the method body contains backward branches. */ public void setBranchesBackward() { branchesBackward = true; } public boolean branchesBackward() { return branchesBackward; } /** * Specifies that the method body invokes super methods. */ public void setInvokesSuperMethods() { invokesSuperMethods = true; } public boolean invokesSuperMethods() { return invokesSuperMethods; } /** * Specifies that the method body invokes methods with * 'invokedynamic'. */ public void setInvokesDynamically() { invokesDynamically = true; } public boolean invokesDynamically() { return invokesDynamically; } /** * Specifies that the method body accesses private fields or methods. */ public void setAccessesPrivateCode() { accessesPrivateCode = true; } public boolean accessesPrivateCode() { return accessesPrivateCode; } /** * Specifies that the method body accesses package visible fields or * methods. */ public void setAccessesPackageCode() { accessesPackageCode = true; } public boolean accessesPackageCode() { return accessesPackageCode; } /** * Specifies that the method body accesses protected fields or methods. */ public void setAccessesProtectedCode() { accessesProtectedCode = true; } public boolean accessesProtectedCode() { return accessesProtectedCode; } /** * Specifies that the method body contains synchronization code * ('monitorenter' and 'monitorexit'). */ public void setHasSynchronizedBlock() { hasSynchronizedBlock = true; } public boolean hasSynchronizedBlock() { return hasSynchronizedBlock; } /** * Specifies that the method body assigns values to final fields. */ public void setAssignsFinalField() { assignsFinalField = true; } public boolean assignsFinalField() { return assignsFinalField; } /** * Specifies that the method body contains `return` instructions that * leave a non-empty stack. */ public void setReturnsWithNonEmptyStack() { returnsWithNonEmptyStack = true; } public boolean returnsWithNonEmptyStack() { return returnsWithNonEmptyStack; } /** * Increments the counter for number of times the method is invoked in the * known code base. */ public void incrementInvocationCount() { invocationCount++; } public int getInvocationCount() { return invocationCount; } /** * Specifies the size that the parameters of the method take up on the stack. * The size takes into account long and double parameters taking up two * entries. */ public synchronized void setParameterSize(int parameterSize) { this.parameterSize = parameterSize; } public int getParameterSize() { return parameterSize; } /** * Returns the stack size of the parameter at the given index. */ public int getParameterSize(int parameterIndex) { return parameterSizes[parameterIndex]; } /** * Specifies that the method actually uses the specified parameter. * * The variable index takes into account long and double parameters * taking up two entries. */ public synchronized void setParameterUsed(int variableIndex) { usedParameters = setBit(usedParameters, variableIndex); } /** * Specifies a mask with the parameters that the method actually uses. * * The indices are variable indices of the variables. They take into * account long and double parameters taking up two entries. */ public synchronized void updateUsedParameters(long usedParameters) { this.usedParameters |= usedParameters; } public boolean hasUnusedParameters() { return parameterSize < 64 ? (usedParameters | -1L << parameterSize) != -1L : usedParameters != -1L ; } public boolean isParameterUsed(int variableIndex) { return isBitSet(usedParameters, variableIndex); } public long getUsedParameters() { return usedParameters; } /** * Notifies this object that a parameter is inserted at the given * index with the given stack size. * @param parameterIndex the parameter index, * not taking into account the entry size, * but taking into account the 'this' parameter, * if any. * @param stackSize the stack size that is occupied by the inserted * parameter. */ public synchronized void insertParameter(int parameterIndex, int stackSize) { // The used parameter bits are indexed with their variable indices // (which take into account the sizes of the entries). //usedParameters = insertBit(usedParameters, parameterIndex, 1L); //parameterSize++; escapedParameters = insertBit(escapedParameters, parameterIndex, 1L); escapingParameters = insertBit(escapingParameters, parameterIndex, 1L); modifiedParameters = insertBit(modifiedParameters, parameterIndex, 1L); returnedParameters = insertBit(returnedParameters, parameterIndex, 1L); parameters = ArrayUtil.insert(parameters, parameters.length, parameterIndex, null); parameterSizes = ArrayUtil.insert(parameterSizes, parameterSizes.length, parameterIndex, stackSize); } /** * Notifies this object that the specified parameter is removed. * @param parameterIndex the parameter index, * not taking into account the entry size, * but taking into account the 'this' parameter, * if any. */ public synchronized void removeParameter(int parameterIndex) { // The used parameter bits are indexed with their variable indices // (which take into account the sizes of the entries). //usedParameters = removeBit(usedParameters, parameterIndex, 1L); //parameterSize--; escapedParameters = removeBit(escapedParameters, parameterIndex, 1L); escapingParameters = removeBit(escapingParameters, parameterIndex, 1L); modifiedParameters = removeBit(modifiedParameters, parameterIndex, 1L); returnedParameters = removeBit(returnedParameters, parameterIndex, 1L); ArrayUtil.remove(parameters, parameters.length, parameterIndex); ArrayUtil.remove(parameterSizes, parameterSizes.length, parameterIndex); } /** * Specifies that the specified reference parameter has already escaped * to the heap when entering the method. * * The parameter index is based on the method descriptor, including 'this', * with each parameter having the same size. */ public synchronized void setParameterEscaped(int parameterIndex) { escapedParameters = setBit(escapedParameters, parameterIndex); } /** * Specifies a mask with the reference parameters have already escaped to * the heap when entering the method. * * The parameter indices are based on the method descriptor, with each * with each parameter having the same size. */ public synchronized void updateEscapedParameters(long escapedParameters) { this.escapedParameters |= escapedParameters; } public boolean hasParameterEscaped(int parameterIndex) { return isBitSet(escapedParameters, parameterIndex); } public long getEscapedParameters() { return escapedParameters; } /** * Specifies that the specified parameter escapes from the method. * An escaping parameter is a reference parameter that the method has made * reachable on the heap after the method has exited. For example, * System#setProperty and Set#add let their parameters escape, because * they become reachable. * * The parameter index is based on the method descriptor, including 'this', * with each parameter having the same size. */ public synchronized void setParameterEscaping(int parameterIndex) { escapingParameters = setBit(escapingParameters, parameterIndex); } /** * Specifies a mask with the parameters that escape from the method. * Escaping parameters are reference parameters that the method has made * reachable on the heap after the method has exited. For example, * System#setProperty and Set#add let their parameters escape, because * they become reachable. * * The parameter indices are based on the method descriptor, with each * with each parameter having the same size. */ public synchronized void updateEscapingParameters(long escapingParameters) { this.escapingParameters |= escapingParameters; } public boolean isParameterEscaping(int parameterIndex) { return !hasNoEscapingParameters && (isBitSet(escapingParameters, parameterIndex)); } public long getEscapingParameters() { return hasNoEscapingParameters ? 0L : escapingParameters; } /** * Specifies that the contents of the specified reference parameter are * modified in the method. * * The parameter index is based on the method descriptor, including 'this', * with each parameter having the same size. * * The methods {@link #setNoSideEffects()} and * {@link #setNoExternalSideEffects()} get precedence. */ public synchronized void setParameterModified(int parameterIndex) { modifiedParameters = setBit(modifiedParameters, parameterIndex); } /** * Specifies a mask of the reference parameters whose contents are modified * in the method. * * The parameter indices are based on the method descriptor, with each * with each parameter having the same size. * * The methods {@link #setNoSideEffects()} and * {@link #setNoExternalSideEffects()} get precedence. */ public synchronized void updateModifiedParameters(long modifiedParameters) { this.modifiedParameters |= modifiedParameters; } public boolean isParameterModified(int parameterIndex) { // TODO: Refine for static methods. return !hasNoSideEffects && (!hasNoExternalSideEffects || parameterIndex == 0) && (isBitSet((modifiesAnything ? modifiedParameters | escapedParameters : modifiedParameters), parameterIndex)); } public long getModifiedParameters() { // TODO: Refine for static methods. return hasNoSideEffects ? 0L : hasNoExternalSideEffects ? modifiedParameters & 1L : modifiedParameters; } /** * Specifies that the method might modify objects that it can reach * through static fields or instance fields. * * The method {@link #setNoExternalSideEffects()} gets precedence. */ public void setModifiesAnything() { modifiesAnything = true; } public boolean modifiesAnything() { return !hasNoExternalSideEffects && modifiesAnything; } /** * Specifies a possible representation of the specified method parameter. * * The parameter index is based on the method descriptor, including 'this', * with each parameter having the same size. */ public synchronized void generalizeParameterValue(int parameterIndex, Value parameter) { parameters[parameterIndex] = parameters[parameterIndex] != null ? parameters[parameterIndex].generalize(parameter) : parameter; } public Value getParameterValue(int parameterIndex) { return parameters != null ? parameters[parameterIndex] : null; } /** * Specifies that the method might return the specified reference parameter * as its result. * * The parameter index is based on the method descriptor, including 'this', * with each parameter having the same size. */ public synchronized void setParameterReturned(int parameterIndex) { returnedParameters = setBit(returnedParameters, parameterIndex); } /** * Specifies a mask of the reference parameters that the method might return. * * The parameter indices are based on the method descriptor, with each * with each parameter having the same size. */ public synchronized void updateReturnedParameters(long returnedParameters) { this.returnedParameters |= returnedParameters; } public boolean returnsParameter(int parameterIndex) { return isBitSet(returnedParameters, parameterIndex); } public long getReturnedParameters() { return returnedParameters; } /** * Specifies that the method might create and return new instances. */ public void setReturnsNewInstances() { returnsNewInstances = true; } public boolean returnsNewInstances() { return returnsNewInstances; } /** * Specifies that the method might return external values. External * return values are reference values that originate from the heap, but * not parameters or new instances. For example, Map#get has external * return values, but StringBuilder#toString has no external return * values. * * The method {@link #setNoExternalReturnValues()} gets precedence. */ public void setReturnsExternalValues() { returnsExternalValues = true; } public boolean returnsExternalValues() { return !hasNoExternalReturnValues && returnsExternalValues; } /** * Specifies a representation of the value that the method returns, or null * if it is unknown. */ public synchronized void generalizeReturnValue(Value returnValue) { this.returnValue = this.returnValue != null ? this.returnValue.generalize(returnValue) : returnValue; } /** * Merges in the given information of a method that is inlined. */ public synchronized void merge(MethodOptimizationInfo other) { this.catchesExceptions |= other.catchesExceptions(); this.branchesBackward |= other.branchesBackward(); this.invokesSuperMethods |= other.invokesSuperMethods(); this.invokesDynamically |= other.invokesDynamically(); this.accessesPrivateCode |= other.accessesPrivateCode(); this.accessesPackageCode |= other.accessesPackageCode(); this.accessesProtectedCode |= other.accessesProtectedCode(); this.hasSynchronizedBlock |= other.hasSynchronizedBlock(); this.assignsFinalField |= other.assignsFinalField(); // Some of these should actually be recomputed, since these are // relative to the method: // this.invokesSuperMethods // this.accessesPrivateCode // this.accessesPackageCode // this.accessesProtectedCode } /** * Creates and sets a ProgramMethodOptimizationInfo instance on the * specified chain of linked methods. */ public static void setProgramMethodOptimizationInfo(Clazz clazz, Method method) { MethodLinker.lastMember(method).setProcessingInfo(new ProgramMethodOptimizationInfo(clazz, method)); } /** * Returns the ProgramMethodOptimizationInfo instance from the specified * chain of linked methods. */ public static ProgramMethodOptimizationInfo getProgramMethodOptimizationInfo(Method method) { return (ProgramMethodOptimizationInfo)MethodLinker.lastMember(method).getProcessingInfo(); } // Small utility methods. /** * Returns the given value with the specified bit set. */ private long setBit(long bits, int index) { return index < 64 ? bits | (1L << index) : bits; } /** * Specifies that the specified bit is set in the given value * (or if the index exceeds the size of the long). */ private boolean isBitSet(long bits, int index) { return index >= 64 || (bits & (1L << index)) != 0; } /** * Returns the given value with a given bit inserted at the given index. */ private long insertBit(long value, int bitIndex, long bitValue) { long higherMask = -1L << bitIndex; long lowerMask = ~higherMask; return ((value & higherMask) << 1) | ( value & lowerMask ) | (bitValue << bitIndex); } /** * Returns the given value with a bit removed at the given index. * The given given bit value is shifted in as the new most significant bit. */ private long removeBit(long value, int bitIndex, long highBitValue) { long higherMask = -1L << bitIndex; long lowerMask = ~higherMask; return ((value & (higherMask<<1)) >>> 1) | ( value & lowerMask ) | (highBitValue << 63); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ReadWriteFieldMarker.java ================================================ /* * 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.info; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.MemberVisitor; /** * This InstructionVisitor marks all fields that are write-only. * * @author Eric Lafortune */ public class ReadWriteFieldMarker implements InstructionVisitor, ConstantVisitor, MemberVisitor { private static final Logger logger = LogManager.getLogger(ReadWriteFieldMarker.class); private final MutableBoolean repeatTrigger; private final boolean markReading; private final boolean markWriting; // Parameters for the visitor methods. // We'll set them to true by default, in case this class is being used // as a field visitor. private boolean reading = true; private boolean writing = true; /** * Creates a new ReadWriteFieldMarker that marks fields that are read and * fields that are written. * @param repeatTrigger a mutable boolean flag that is set whenever a field * gets a mark that it didn't have before. */ public ReadWriteFieldMarker(MutableBoolean repeatTrigger) { this(repeatTrigger, true, true); } /** * Creates a new ReadWriteFieldMarker that marks fields that are read and * fields that are written, as specified. * @param repeatTrigger a mutable boolean flag that is set whenever a field * gets a mark that it didn't have before. * @param markReading specifies whether fields may be marked as read. * @param markWriting specifies whether fields may be marked as written. */ public ReadWriteFieldMarker(MutableBoolean repeatTrigger, boolean markReading, boolean markWriting) { this.repeatTrigger = repeatTrigger; this.markReading = markReading; this.markWriting = markWriting; } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { byte opcode = constantInstruction.opcode; // Check for instructions that involve fields. switch (opcode) { case Instruction.OP_LDC: case Instruction.OP_LDC_W: // Mark the field, if any, as being read from and written to. reading = true; writing = true; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; case Instruction.OP_GETSTATIC: case Instruction.OP_GETFIELD: // Mark the field as being read from. reading = true; writing = false; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; case Instruction.OP_PUTSTATIC: case Instruction.OP_PUTFIELD: // Mark the field as being written to. reading = false; writing = true; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Mark the referenced field, if any. stringConstant.referencedMemberAccept(this); } public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { // Mark the referenced field. fieldrefConstant.referencedFieldAccept(this); } // Implementations for MemberVisitor. public void visitAnyMember(Clazz Clazz, Member member) {} public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Mark the field if it is being read from. if (markReading && reading) { markAsRead(programClass, programField); } // Mark the field if it is being written to. if (markWriting && writing) { markAsWritten(programClass, programField); } } // Small utility methods. private void markAsRead(Clazz clazz, Field field) { FieldOptimizationInfo fieldOptimizationInfo = FieldOptimizationInfo.getFieldOptimizationInfo(field); if (!fieldOptimizationInfo.isRead() && fieldOptimizationInfo instanceof ProgramFieldOptimizationInfo) { logger.debug("ReadWriteFieldMarker: marking as read: {}.{}", clazz.getName(), field.getName(clazz)); ((ProgramFieldOptimizationInfo)fieldOptimizationInfo).setRead(); repeatTrigger.set(); } } public static boolean isRead(Field field) { return FieldOptimizationInfo.getFieldOptimizationInfo(field).isRead(); } private void markAsWritten(Clazz clazz, Field field) { FieldOptimizationInfo fieldOptimizationInfo = FieldOptimizationInfo.getFieldOptimizationInfo(field); if (!fieldOptimizationInfo.isWritten() && fieldOptimizationInfo instanceof ProgramFieldOptimizationInfo) { logger.debug("ReadWriteFieldMarker: marking as written: {}.{}", clazz.getName(), field.getName(clazz)); ((ProgramFieldOptimizationInfo)fieldOptimizationInfo).setWritten(); repeatTrigger.set(); } } public static boolean isWritten(Field field) { return FieldOptimizationInfo.getFieldOptimizationInfo(field).isWritten(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/ReferenceEscapeChecker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.ClassEstimates; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.evaluation.*; import proguard.evaluation.value.*; import proguard.optimize.evaluation.*; import proguard.util.ArrayUtil; /** * This AttributeVisitor can tell whether reference parameters and instances * are escaping, are modified, or are returned. * * @see ParameterEscapeMarker * @author Eric Lafortune */ public class ReferenceEscapeChecker implements AttributeVisitor, InstructionVisitor, ConstantVisitor { private static final Logger logger = LogManager.getLogger(ReferenceEscapeChecker.class); private boolean[] instanceEscaping = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; private boolean[] instanceReturned = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; private boolean[] instanceModified = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; private boolean[] externalInstance = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; // private boolean[] exceptionEscaping = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; // private boolean[] exceptionReturned = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; // private boolean[] exceptionModified = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; private final PartialEvaluator partialEvaluator; private final boolean runPartialEvaluator; // Parameters and values for visitor methods. private Method referencingMethod; private int referencingOffset; private int referencingPopCount; /** * Creates a new ReferenceEscapeChecker. */ public ReferenceEscapeChecker() { this(new ReferenceTracingValueFactory(new BasicValueFactory())); } /** * Creates a new ReferenceEscapeChecker. This private constructor gets around * the constraint that it's not allowed to add statements before calling * 'this'. */ private ReferenceEscapeChecker(ReferenceTracingValueFactory referenceTracingValueFactory) { this(new PartialEvaluator(referenceTracingValueFactory, new ParameterTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory)), true, referenceTracingValueFactory), true); } /** * Creates a new ReferenceEscapeChecker. * @param partialEvaluator the evaluator to be used for the analysis. * @param runPartialEvaluator specifies whether to run this evaluator on * every code attribute that is visited. */ public ReferenceEscapeChecker(PartialEvaluator partialEvaluator, boolean runPartialEvaluator) { this.partialEvaluator = partialEvaluator; this.runPartialEvaluator = runPartialEvaluator; } /** * Returns whether the instance created or retrieved at the specified * instruction offset is escaping. */ public boolean isInstanceEscaping(int instructionOffset) { return instanceEscaping[instructionOffset]; } /** * Returns whether the instance created or retrieved at the specified * instruction offset is being returned. */ public boolean isInstanceReturned(int instructionOffset) { return instanceReturned[instructionOffset]; } /** * Returns whether the instance created or retrieved at the specified * instruction offset is being modified. */ public boolean isInstanceModified(int instructionOffset) { return instanceModified[instructionOffset]; } /** * Returns whether the instance created or retrieved at the specified * instruction offset is external to this method and its invoked methods. */ public boolean isInstanceExternal(int instructionOffset) { return externalInstance[instructionOffset]; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Evaluate the method. if (runPartialEvaluator) { partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); } int codeLength = codeAttribute.u4codeLength; // Initialize the global arrays. instanceEscaping = ArrayUtil.ensureArraySize(instanceEscaping, codeLength, false); instanceReturned = ArrayUtil.ensureArraySize(instanceReturned, codeLength, false); instanceModified = ArrayUtil.ensureArraySize(instanceModified, codeLength, false); externalInstance = ArrayUtil.ensureArraySize(externalInstance, codeLength, false); // Mark the parameters and instances that are escaping from the code. codeAttribute.instructionsAccept(clazz, method, partialEvaluator.tracedInstructionFilter(this)); logger.debug("ReferenceEscapeChecker: [{}.{}{}]", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz) ); if (logger.getLevel().isLessSpecificThan(Level.DEBUG)) { for (int index = 0; index < codeLength; index++) { if (partialEvaluator.isInstruction(index)) { logger.debug(" {}{}{}{} {}", instanceEscaping[index] ? 'E' : '.', instanceReturned[index] ? 'R' : '.', instanceModified[index] ? 'M' : '.', externalInstance[index] ? 'X' : '.', InstructionFactory.create(codeAttribute.code, index).toString(index) ); } } } } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { switch (simpleInstruction.opcode) { case Instruction.OP_AASTORE: // Mark array reference values whose element is modified. markModifiedReferenceValues(offset, simpleInstruction.stackPopCount(clazz) - 1); // Mark reference values that are put in the array. markEscapingReferenceValues(offset, 0); break; case Instruction.OP_IASTORE: case Instruction.OP_LASTORE: case Instruction.OP_FASTORE: case Instruction.OP_DASTORE: case Instruction.OP_BASTORE: case Instruction.OP_CASTORE: case Instruction.OP_SASTORE: // Mark array reference values whose element is modified. markModifiedReferenceValues(offset, simpleInstruction.stackPopCount(clazz) - 1); break; case Instruction.OP_ARETURN: // Mark the returned reference values. markReturnedReferenceValues(offset, 0); break; case Instruction.OP_ATHROW: // Mark the escaping reference values. markEscapingReferenceValues(offset, 0); break; } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_GETSTATIC: case Instruction.OP_GETFIELD: // Mark external reference values. markExternalReferenceValue(offset); break; case Instruction.OP_PUTSTATIC: // Mark reference values that are put in the field. markEscapingReferenceValues(offset, 0); break; case Instruction.OP_PUTFIELD: // Mark reference reference values whose field is modified. markModifiedReferenceValues(offset, constantInstruction.stackPopCount(clazz) - 1); // Mark reference values that are put in the field. markEscapingReferenceValues(offset, 0); break; case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: case Instruction.OP_INVOKEINTERFACE: // Mark reference reference values that are modified as parameters // of the invoked method. // Mark reference values that are escaping as parameters // of the invoked method. // Mark escaped reference reference values in the invoked method. referencingMethod = method; referencingOffset = offset; referencingPopCount = constantInstruction.stackPopCount(clazz); clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, this); } public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { Method referencedMethod = (Method)anyMethodrefConstant.referencedMethod; // Mark reference reference values that are passed to the method. for (int index = 0; index < referencingPopCount; index++) { int stackEntryIndex = referencingPopCount - index - 1; TracedStack stackBefore = partialEvaluator.getStackBefore(referencingOffset); Value stackEntry = stackBefore.getTop(stackEntryIndex); if (stackEntry.computationalType() == Value.TYPE_REFERENCE) { // Is the parameter escaping from the referenced method? if (referencedMethod == null || ParameterEscapeMarker.isParameterEscaping(referencedMethod, index)) { markEscapingReferenceValues(referencingOffset, stackEntryIndex); } // Is the parameter being modified in the referenced method? if (referencedMethod == null || ParameterEscapeMarker.isParameterModified(referencedMethod, index)) { markModifiedReferenceValues(referencingOffset, stackEntryIndex); } } } // Is the return value from the referenced method external? String returnType = ClassUtil.internalMethodReturnType(anyMethodrefConstant.getType(clazz)); if (referencedMethod == null || ((ClassUtil.isInternalClassType(returnType) || ClassUtil.isInternalArrayType(returnType)) && ParameterEscapeMarker.returnsExternalValues(referencedMethod))) { markExternalReferenceValue(referencingOffset); } } // Small utility methods. /** * Marks the producing offsets of the specified stack entry at the given * instruction offset. */ private void markEscapingReferenceValues(int instructionOffset, int stackEntryIndex) { TracedStack stackBefore = partialEvaluator.getStackBefore(instructionOffset); Value stackEntry = stackBefore.getTop(stackEntryIndex); if (stackEntry.computationalType() == Value.TYPE_REFERENCE) { ReferenceValue referenceValue = stackEntry.referenceValue(); // The null reference value may not have a trace value. if (referenceValue.isNull() != Value.ALWAYS) { markEscapingReferenceValues(referenceValue); } } } /** * Marks the producing offsets of the given traced reference value. */ private void markEscapingReferenceValues(ReferenceValue referenceValue) { TracedReferenceValue tracedReferenceValue = (TracedReferenceValue)referenceValue; InstructionOffsetValue instructionOffsetValue = tracedReferenceValue.getTraceValue().instructionOffsetValue(); int parameterCount = instructionOffsetValue.instructionOffsetCount(); for (int index = 0; index < parameterCount; index++) { if (!instructionOffsetValue.isMethodParameter(index)) { instanceEscaping[instructionOffsetValue.instructionOffset(index)] = true; } } } /** * Marks the producing offsets of the specified stack entry at the given * instruction offset. */ private void markReturnedReferenceValues(int instructionOffset, int stackEntryIndex) { TracedStack stackBefore = partialEvaluator.getStackBefore(instructionOffset); ReferenceValue referenceValue = stackBefore.getTop(stackEntryIndex).referenceValue(); // The null reference value may not have a trace value. if (referenceValue.isNull() != Value.ALWAYS) { markReturnedReferenceValues(referenceValue); } } /** * Marks the producing offsets of the given traced reference value. */ private void markReturnedReferenceValues(ReferenceValue referenceValue) { TracedReferenceValue tracedReferenceValue = (TracedReferenceValue)referenceValue; InstructionOffsetValue instructionOffsetValue = tracedReferenceValue.getTraceValue().instructionOffsetValue(); int parameterCount = instructionOffsetValue.instructionOffsetCount(); for (int index = 0; index < parameterCount; index++) { if (!instructionOffsetValue.isMethodParameter(index)) { instanceReturned[instructionOffsetValue.instructionOffset(index)] = true; } } } /** * Marks the producing offsets of the specified stack entry at the given * instruction offset. */ private void markModifiedReferenceValues(int instructionOffset, int stackEntryIndex) { TracedStack stackBefore = partialEvaluator.getStackBefore(instructionOffset); ReferenceValue referenceValue = stackBefore.getTop(stackEntryIndex).referenceValue(); // The null reference value may not have a trace value. if (referenceValue.isNull() != Value.ALWAYS) { markModifiedReferenceValues(referenceValue); } } /** * Marks the producing offsets of the given traced reference value. */ private void markModifiedReferenceValues(ReferenceValue referenceValue) { TracedReferenceValue tracedReferenceValue = (TracedReferenceValue)referenceValue; InstructionOffsetValue instructionOffsetValue = tracedReferenceValue.getTraceValue().instructionOffsetValue(); int parameterCount = instructionOffsetValue.instructionOffsetCount(); for (int index = 0; index < parameterCount; index++) { if (!instructionOffsetValue.isMethodParameter(index)) { instanceModified[instructionOffsetValue.instructionOffset(index)] = true; } } } /** * Marks the producing offsets of the specified stack entry at the given * instruction offset. */ private void markExternalReferenceValue(int offset) { externalInstance[offset] = true; } } ================================================ FILE: base/src/main/java/proguard/optimize/info/RepeatedClassPoolVisitor.java ================================================ /* * 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.info; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.ClassPool; import proguard.classfile.visitor.ClassPoolVisitor; /** * This ClassPoolVisitor repeatedly delegates to a given class pool visitor, as * long as it keeps setting a given flag. * * @author Eric Lafortune */ public class RepeatedClassPoolVisitor implements ClassPoolVisitor { private static final Logger logger = LogManager.getLogger(RepeatedClassPoolVisitor.class); private final MutableBoolean repeatTrigger; private final ClassPoolVisitor classPoolVisitor; /** * Creates a new RepeatedClassPoolVisitor. * @param repeatTrigger the mutable boolean flag that the class pool * visitor can set to indicate that the class pool * should be visited again. * @param classPoolVisitor the class pool visitor to apply. */ public RepeatedClassPoolVisitor(MutableBoolean repeatTrigger, ClassPoolVisitor classPoolVisitor) { this.repeatTrigger = repeatTrigger; this.classPoolVisitor = classPoolVisitor; } // Implementations for ClassPoolVisitor. public void visitClassPool(ClassPool classPool) { // Visit all classes at least once, until the class visitors stop // setting the repeat trigger. do { logger.debug("RepeatedClassPoolVisitor: new iteration"); repeatTrigger.reset(); // Visit over all classes once. classPoolVisitor.visitClassPool(classPool); } while (repeatTrigger.isSet()); logger.debug("RepeatedClassPoolVisitor: done iterating"); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/SideEffectClassChecker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassCollector; import java.util.*; /** * This utility class contains methods to check whether referencing classes * may have side effects due to them being loaded and initialized. * * @see NoSideEffectClassMarker * @see SideEffectClassMarker * @author Eric Lafortune */ public class SideEffectClassChecker { /** * Returns whether accessing the given class member from the given class may * have side effects when they are initialized. */ public static boolean mayHaveSideEffects(Clazz referencingClass, Clazz referencedClass, Member referencedMember) { // Is the referenced class member static or an initializer method? // Does accessing the referenced class then have side effects? return ((referencedMember.getAccessFlags() & AccessConstants.STATIC) != 0 || referencedMember.getName(referencedClass).equals(ClassConstants.METHOD_NAME_INIT)) && mayHaveSideEffects(referencingClass, referencedClass); } /** * Returns whether accessing the given class from another given class may * have side effects when they are initialized. */ public static boolean mayHaveSideEffects(Clazz referencingClass, Clazz referencedClass) { return !NoSideEffectClassMarker.hasNoSideEffects(referencedClass) && !referencingClass.extendsOrImplements(referencedClass) && !sideEffectSuperClasses(referencingClass).containsAll(sideEffectSuperClasses(referencedClass)); } /** * Returns the set of superclasses and interfaces that are initialized. */ private static Set sideEffectSuperClasses(Clazz clazz) { Set set = new HashSet(); // Visit all superclasses and interfaces, collecting the ones that have // side effects when they are initialized. clazz.hierarchyAccept(true, true, true, false, new SideEffectClassFilter( new ClassCollector(set))); return set; } } ================================================ FILE: base/src/main/java/proguard/optimize/info/SideEffectClassFilter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates all its method calls to another ClassVisitor, * but only for Clazz objects that have side effects when they are initialized. * * @author Eric Lafortune */ public class SideEffectClassFilter implements ClassVisitor { private final ClassVisitor classVisitor; public SideEffectClassFilter(ClassVisitor classVisitor) { this.classVisitor = classVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { if (SideEffectClassMarker.hasSideEffects(clazz)) { clazz.accept(classVisitor); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/SideEffectClassMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.*; /** * This ClassVisitor marks all classes that it visits as having side effects. * * @author Eric Lafortune */ public class SideEffectClassMarker implements ClassVisitor { // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { markSideEffects(programClass); } // Small utility methods. private static void markSideEffects(Clazz clazz) { ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setSideEffects(); } public static boolean hasSideEffects(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).hasSideEffects(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/SideEffectInstructionChecker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.MemberVisitor; /** * This class can tell whether an instruction has any side effects. This * includes invoking methods that have side effects, writing to fields that * are not write-only, and throwing exceptions. Return instructions and * array store instructions can be included or not. * * With the environment setting "optimize.conservatively", it also accounts for * all possible NullPointerExceptions, ArrayIndexOutOfBoundExceptions, etc., * which are typically accidental, not intentional. * * @see ReadWriteFieldMarker * @see SideEffectClassMarker * @see NoSideEffectMethodMarker * @see SideEffectMethodMarker * @author Eric Lafortune */ public class SideEffectInstructionChecker implements InstructionVisitor, ConstantVisitor, MemberVisitor { private final boolean includeReturnInstructions; private final boolean includeArrayStoreInstructions; private final boolean includeBuiltInExceptions; // Parameters and return values for the visitor methods. private boolean writingField; private Clazz referencingClass; private boolean hasSideEffects; /** * Creates a new SideEffectInstructionChecker * @param includeReturnInstructions specifies whether return * instructions count as side * effects. * @param includeArrayStoreInstructions specifies whether storing values * in arrays counts as side effects. * @param includeBuiltInExceptions specifies whether built-in exceptions * count as side effects (e.g. calling getfield * on null object reference) */ public SideEffectInstructionChecker(boolean includeReturnInstructions, boolean includeArrayStoreInstructions, boolean includeBuiltInExceptions) { this.includeReturnInstructions = includeReturnInstructions; this.includeArrayStoreInstructions = includeArrayStoreInstructions; this.includeBuiltInExceptions = includeBuiltInExceptions; } public boolean hasSideEffects(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { hasSideEffects = false; instruction.accept(clazz, method, codeAttribute, offset, this); return hasSideEffects; } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { byte opcode = simpleInstruction.opcode; // Check for instructions that might cause side effects. switch (opcode) { case Instruction.OP_IDIV: case Instruction.OP_LDIV: case Instruction.OP_IREM: case Instruction.OP_LREM: case Instruction.OP_FDIV: case Instruction.OP_FREM: case Instruction.OP_DDIV: case Instruction.OP_DREM: case Instruction.OP_IALOAD: case Instruction.OP_LALOAD: case Instruction.OP_FALOAD: case Instruction.OP_DALOAD: case Instruction.OP_AALOAD: case Instruction.OP_BALOAD: case Instruction.OP_CALOAD: case Instruction.OP_SALOAD: case Instruction.OP_NEWARRAY: case Instruction.OP_ARRAYLENGTH: // These instructions strictly taken may cause a side effect // (ArithmeticException, NullPointerException, // ArrayIndexOutOfBoundsException, NegativeArraySizeException). hasSideEffects = includeBuiltInExceptions; break; case Instruction.OP_IASTORE: case Instruction.OP_LASTORE: case Instruction.OP_FASTORE: case Instruction.OP_DASTORE: case Instruction.OP_AASTORE: case Instruction.OP_BASTORE: case Instruction.OP_CASTORE: case Instruction.OP_SASTORE: // These instructions may cause a side effect. hasSideEffects = includeArrayStoreInstructions; break; case Instruction.OP_ATHROW : case Instruction.OP_MONITORENTER: case Instruction.OP_MONITOREXIT: // These instructions always cause a side effect. hasSideEffects = true; break; case Instruction.OP_IRETURN: case Instruction.OP_LRETURN: case Instruction.OP_FRETURN: case Instruction.OP_DRETURN: case Instruction.OP_ARETURN: case Instruction.OP_RETURN: // These instructions may have a side effect. hasSideEffects = includeReturnInstructions; break; } } public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { byte opcode = variableInstruction.opcode; // Check for instructions that might cause side effects. switch (opcode) { case Instruction.OP_RET: // This instruction may have a side effect. hasSideEffects = includeReturnInstructions; break; } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { byte opcode = constantInstruction.opcode; // Check for instructions that might cause side effects. switch (opcode) { case Instruction.OP_GETSTATIC: // Check if accessing the field might cause any side effects. writingField = false; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; case Instruction.OP_PUTSTATIC: // Check if accessing the field might cause any side effects. writingField = true; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; case Instruction.OP_GETFIELD: if (includeBuiltInExceptions) { // These instructions strictly taken may cause a side effect // (NullPointerException). hasSideEffects = true; } else { // Check if the field is write-only or volatile. writingField = false; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } break; case Instruction.OP_PUTFIELD: if (includeBuiltInExceptions) { // These instructions strictly taken may cause a side effect // (NullPointerException). hasSideEffects = true; } else { // Check if the field is write-only or volatile. writingField = true; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } break; case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: // Check if the invoked method is causing any side effects. clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); break; case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKEINTERFACE: case Instruction.OP_INVOKEDYNAMIC: if (includeBuiltInExceptions) { // These instructions strictly taken may cause a side effect // (NullPointerException). hasSideEffects = true; } else { // Check if the invoked method is causing any side effects. clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } break; case Instruction.OP_ANEWARRAY: case Instruction.OP_MULTIANEWARRAY: case Instruction.OP_CHECKCAST: // This instructions strictly taken may cause a side effect // (ClassCastException, NegativeArraySizeException). hasSideEffects = includeBuiltInExceptions; break; } } public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { byte opcode = branchInstruction.opcode; // Check for instructions that might cause side effects. switch (opcode) { case Instruction.OP_JSR: case Instruction.OP_JSR_W: hasSideEffects = includeReturnInstructions; break; } } // Implementations for ConstantVisitor. public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { // We'll have to assume invoking an unknown method has side effects. hasSideEffects = true; } public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { // Pass the referencing class. referencingClass = clazz; // We'll have to assume accessing an unknown field has side effects. hasSideEffects = true; // Check the referenced field, if known. fieldrefConstant.referencedFieldAccept(this); } public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { // Pass the referencing class. referencingClass = clazz; // We'll have to assume invoking an unknown method has side effects. hasSideEffects = true; // Check the referenced method, if known. anyMethodrefConstant.referencedMethodAccept(this); } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { hasSideEffects = (writingField && ReadWriteFieldMarker.isRead(programField)) || (programField.getAccessFlags() & AccessConstants.VOLATILE) != 0 || SideEffectClassChecker.mayHaveSideEffects(referencingClass, programClass, programField); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Note that side effects already include synchronization of some // implementation of the method. hasSideEffects = SideEffectMethodMarker.hasSideEffects(programMethod) || SideEffectClassChecker.mayHaveSideEffects(referencingClass, programClass, programMethod); } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { hasSideEffects = true; } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { hasSideEffects = !NoSideEffectMethodMarker.hasNoSideEffects(libraryMethod); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/SideEffectMethodFilter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor delegates all its method calls to another MemberVisitor, * but only for Method objects that are marked as having side effects. * * @see SideEffectMethodMarker * * @author Eric Lafortune */ public class SideEffectMethodFilter implements MemberVisitor { private final MemberVisitor memberVisitor; /** * Creates a new SideEffectMethodFilter. * @param memberVisitor the member visitor to which the visiting will be * delegated. */ public SideEffectMethodFilter(MemberVisitor memberVisitor) { this.memberVisitor = memberVisitor; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) {} public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (SideEffectMethodMarker.hasSideEffects(programMethod)) { memberVisitor.visitProgramMethod(programClass, programMethod); } } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { if (SideEffectMethodMarker.hasSideEffects(libraryMethod)) { memberVisitor.visitLibraryMethod(libraryClass, libraryMethod); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/SideEffectMethodMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.*; import proguard.optimize.*; /** * This MemberVisitor and InstructionVisitor marks all methods and classes * that have side effects. This includes invoking other methods that have side * effects, writing to fields that are not write-only, and throwing exceptions. * * @see NoSideEffectMethodMarker * @author Eric Lafortune */ public class SideEffectMethodMarker implements MemberVisitor, InstructionVisitor { private static final Logger logger = LogManager.getLogger(SideEffectMethodMarker.class); private final MemberVisitor extraMemberVisitor; private final SideEffectInstructionChecker sideEffectInstructionChecker; private final ClassVisitor sideEffectClassMarker = new OptimizationInfoClassFilter( new SideEffectClassMarker()); /** * Creates a new SideEffectMethodMarker. * @param optimizeConservatively specifies whether conservative * optimization should be applied */ public SideEffectMethodMarker(boolean optimizeConservatively) { this(null, optimizeConservatively); } /** * Creates a new SideEffectMethodMarker. * * @param extraMemberVisitor optional visitor to apply to marked methods * @param optimizeConservatively specifies whether conservative optimization * should be applied */ public SideEffectMethodMarker(MemberVisitor extraMemberVisitor, boolean optimizeConservatively) { this.extraMemberVisitor = extraMemberVisitor; this.sideEffectInstructionChecker = new SideEffectInstructionChecker(false, true, optimizeConservatively); } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if ((programMethod.getAccessFlags() & (AccessConstants.NATIVE | AccessConstants.SYNCHRONIZED)) != 0) { markSideEffects(programClass, programMethod); } } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // Check if it may be throwing exceptions. if (sideEffectInstructionChecker.hasSideEffects(clazz, method, codeAttribute, offset, instruction)) { markSideEffects(clazz, method); } } // Small utility methods. private void markSideEffects(Clazz clazz, Method method) { MethodOptimizationInfo methodOptimizationInfo = MethodOptimizationInfo.getMethodOptimizationInfo(method); if (!methodOptimizationInfo.hasSideEffects() && methodOptimizationInfo instanceof ProgramMethodOptimizationInfo) { ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setSideEffects(); // Trigger the repeater if the setter has changed the value. if (methodOptimizationInfo.hasSideEffects()) { logger.debug("SideEffectMethodMarker: marking for side-effects: {}.{}{}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz) ); if (extraMemberVisitor != null) { method.accept(clazz, extraMemberVisitor); } // Also mark the class if the method is a static initializer. if (method.getName(clazz).equals(ClassConstants.METHOD_NAME_CLINIT)) { clazz.accept(sideEffectClassMarker); } } } } public static boolean hasSideEffects(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).hasSideEffects(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/SimpleEnumFilter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates its visits to one of two other given * ClassVisitor instances, depending on whether the classes are marked * as simple enums or not. * * @see SimpleEnumMarker * * @author Eric Lafortune */ public class SimpleEnumFilter implements ClassVisitor { private final ClassVisitor simpleEnumClassVisitor; private final ClassVisitor otherClassVisitor; /** * Creates a new SimpleEnumClassFilter. * * @param simpleEnumClassVisitor the class visitor to which visits to * classes that are marked to be simpleEnum * will be delegated. */ public SimpleEnumFilter(ClassVisitor simpleEnumClassVisitor) { this(simpleEnumClassVisitor, null); } /** * Creates a new SimpleEnumClassFilter. * * @param simpleEnumClassVisitor the class visitor to which visits to * classes that are marked as simple enums * will be delegated. * @param otherClassVisitor the class visitor to which visits to * classes that are not marked as simple * enums will be delegated. */ public SimpleEnumFilter(ClassVisitor simpleEnumClassVisitor, ClassVisitor otherClassVisitor) { this.simpleEnumClassVisitor = simpleEnumClassVisitor; this.otherClassVisitor = otherClassVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { // Is the class marked as a simple enum? ClassVisitor classVisitor = SimpleEnumMarker.isSimpleEnum(programClass) ? simpleEnumClassVisitor : otherClassVisitor; if (classVisitor != null) { classVisitor.visitProgramClass(programClass); } } @Override public void visitLibraryClass(LibraryClass libraryClass) { // A library class can't be marked as a simple enum. if (otherClassVisitor != null) { otherClassVisitor.visitLibraryClass(libraryClass); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/SimpleEnumMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor marks all program classes that it visits with a given * flag for simple enums. * * @author Eric Lafortune */ public class SimpleEnumMarker implements ClassVisitor { private final boolean simple; /** * Creates a new SimpleEnumMarker that marks visited classes with the * given flag. */ public SimpleEnumMarker(boolean simple) { this.simple = simple; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { setSimpleEnum(programClass); } // Small utility methods. private void setSimpleEnum(Clazz clazz) { ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setSimpleEnum(simple); } public static boolean isSimpleEnum(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).isSimpleEnum(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/SuperInvocationMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This InstructionVisitor marks all methods that invoke super methods (other * than initializers) from the instructions that it visits. * * @author Eric Lafortune */ public class SuperInvocationMarker implements InstructionVisitor, ConstantVisitor { private boolean invokesSuperMethods; // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_INVOKESPECIAL) { invokesSuperMethods = false; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); if (invokesSuperMethods) { setInvokesSuperMethods(method); } } } // Implementations for ConstantVisitor. public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { invokesSuperMethods = !clazz.equals(anyMethodrefConstant.referencedClass) && !anyMethodrefConstant.getName(clazz).equals(ClassConstants.METHOD_NAME_INIT); } // Small utility methods. private static void setInvokesSuperMethods(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setInvokesSuperMethods(); } public static boolean invokesSuperMethods(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).invokesSuperMethods(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/SynchronizedBlockMethodMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This InstructionVisitor marks the existence of synchronized blocks * of the methods whose instructions it visits. * * @author Thomas Neidhart */ public class SynchronizedBlockMethodMarker implements InstructionVisitor { // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { if (simpleInstruction.opcode == Instruction.OP_MONITORENTER || simpleInstruction.opcode == Instruction.OP_MONITOREXIT) { setHasSynchronizedBlock(method); } } // Small utility methods. private static void setHasSynchronizedBlock(Method method) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method).setHasSynchronizedBlock(); } /** * Returns whether the given method accesses private class members. */ public static boolean hasSynchronizedBlock(Method method) { return MethodOptimizationInfo.getMethodOptimizationInfo(method).hasSynchronizedBlock(); } } ================================================ FILE: base/src/main/java/proguard/optimize/info/UnusedParameterMethodFilter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor delegates all its method calls to another MemberVisitor, * but only for Method objects that are marked as having unused parameters. * * @see ParameterUsageMarker * * @author Eric Lafortune */ public class UnusedParameterMethodFilter implements MemberVisitor { private final MemberVisitor memberVisitor; /** * Creates a new UnusedParameterMethodFilter. * @param memberVisitor the member visitor to which the visiting will be * delegated. */ public UnusedParameterMethodFilter(MemberVisitor memberVisitor) { this.memberVisitor = memberVisitor; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) {} public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {} public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (ParameterUsageMarker.hasUnusedParameters(programMethod)) { memberVisitor.visitProgramMethod(programClass, programMethod); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/UnusedParameterOptimizationInfoUpdater.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.info; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.optimize.*; import java.util.*; /** * This AttributeVisitor removes unused parameters from the optimization info * of the methods that it visits. This includes 'this' parameters. * * @see ParameterUsageMarker * @see MethodStaticizer * @see MethodDescriptorShrinker * @author Eric Lafortune */ public class UnusedParameterOptimizationInfoUpdater implements AttributeVisitor { private static final Logger logger = LogManager.getLogger(UnusedParameterOptimizationInfoUpdater.class); private final MemberVisitor extraUnusedParameterMethodVisitor; /** * Creates a new UnusedParameterOptimizationInfoUpdater. */ public UnusedParameterOptimizationInfoUpdater() { this(null); } /** * Creates a new UnusedParameterOptimizationInfoUpdater with an extra * visitor. * @param extraUnusedParameterMethodVisitor an optional extra visitor for * all removed parameters. */ public UnusedParameterOptimizationInfoUpdater(MemberVisitor extraUnusedParameterMethodVisitor) { this.extraUnusedParameterMethodVisitor = extraUnusedParameterMethodVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { logger.debug("UnusedParameterOptimizationInfoUpdater: {}.{}{}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz) ); // Get the original parameter size that was saved. int oldParameterSize = ParameterUsageMarker.getParameterSize(method); // Compute the new parameter size from the shrunk descriptor. int newParameterSize = ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz), method.getAccessFlags()); if (oldParameterSize > newParameterSize) { ProgramMethodOptimizationInfo programMethodOptimizationInfo = ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(method); List removedParameters = new ArrayList(); for (int parameterIndex = 0, variableIndex = 0; variableIndex < oldParameterSize; parameterIndex++) { // Is the variable required as a parameter? if (!ParameterUsageMarker.isParameterUsed(method, variableIndex)) { logger.debug(" Deleting parameter #{} (v{})", parameterIndex, variableIndex); removedParameters.add(parameterIndex); } variableIndex += programMethodOptimizationInfo.getParameterSize(parameterIndex); } // Remove the parameters in reverse order. for (int i = removedParameters.size() - 1; i >= 0; i--) { programMethodOptimizationInfo.removeParameter(removedParameters.get(i)); } programMethodOptimizationInfo.setParameterSize(newParameterSize); programMethodOptimizationInfo.updateUsedParameters(-1L); // Visit the method, if required. if (extraUnusedParameterMethodVisitor != null) { method.accept(clazz, extraUnusedParameterMethodVisitor); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/UsedParameterFilter.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.visitor.*; /** * This ParameterVisitor delegates all its visits to one of two other * ParameterVisitor instances, depending on whether the parameter is * used or not. * * @see ParameterUsageMarker * @author Eric Lafortune */ public class UsedParameterFilter implements ParameterVisitor { private final ParameterVisitor usedParameterVisitor; private final ParameterVisitor unusedParameterVisitor; /** * Creates a new UsedParameterFilter that delegates visits to used * parameters to the given parameter visitor. */ public UsedParameterFilter(ParameterVisitor usedParameterVisitor) { this(usedParameterVisitor, null); } /** * Creates a new UsedParameterFilter that delegates to one of the two * given parameter visitors. */ public UsedParameterFilter(ParameterVisitor usedParameterVisitor, ParameterVisitor unusedParameterVisitor) { this.usedParameterVisitor = usedParameterVisitor; this.unusedParameterVisitor = unusedParameterVisitor; } // Implementations for ParameterVisitor. public void visitParameter(Clazz clazz, Member member, int parameterIndex, int parameterCount, int parameterOffset, int parameterSize, String parameterType, Clazz referencedClass) { ParameterVisitor parameterVisitor = ParameterUsageMarker.isParameterUsed((Method)member, parameterOffset) ? usedParameterVisitor : unusedParameterVisitor; if (parameterVisitor != null) { parameterVisitor.visitParameter(clazz, member, parameterIndex, parameterCount, parameterOffset, parameterSize, parameterType, referencedClass); } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/VariableUsageMarker.java ================================================ /* * 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.info; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.ClassEstimates; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import java.util.Arrays; /** * This AttributeVisitor marks the local variables that are used in the code * attributes that it visits. * * @author Eric Lafortune */ public class VariableUsageMarker implements AttributeVisitor, InstructionVisitor { private boolean[] variableUsed = new boolean[ClassEstimates.TYPICAL_VARIABLES_SIZE]; /** * Returns whether the given variable has been marked as being used. */ public boolean isVariableUsed(int variableIndex) { return variableUsed[variableIndex]; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { int maxLocals = codeAttribute.u2maxLocals; // Try to reuse the previous array. if (variableUsed.length < maxLocals) { // Create a new array. variableUsed = new boolean[maxLocals]; } else { // Reset the array. Arrays.fill(variableUsed, 0, maxLocals, false); } codeAttribute.instructionsAccept(clazz, method, this); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { // Mark the variable. variableUsed[variableInstruction.variableIndex] = true; // Account for Category 2 instructions, which take up two entries. if (variableInstruction.stackPopCount(clazz) == 2 || variableInstruction.stackPushCount(clazz) == 2) { variableUsed[variableInstruction.variableIndex + 1] = true; } } } ================================================ FILE: base/src/main/java/proguard/optimize/info/WrapperClassMarker.java ================================================ /* * 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.info; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.*; import proguard.classfile.instruction.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.evaluation.value.*; import proguard.optimize.evaluation.StoringInvocationUnit; /** * This ClassVisitor marks all program classes that are a simple wrapper for a * single non-null instance of another class. * * A wrapper class has * - exactly one non-static field, which references an object, * - exactly one initializer, with a single parameter that is never null, * that initializes the field, * - no subclasses. * * @see StoringInvocationUnit * @author Eric Lafortune */ public class WrapperClassMarker implements ClassVisitor, MemberVisitor, AttributeVisitor { private static final Logger logger = LogManager.getLogger(WrapperClassMarker.class); private final Constant[] INITIALIZER_CONSTANTS = new Constant[] { new FieldrefConstant(InstructionSequenceMatcher.A, InstructionSequenceMatcher.B, null, null), }; // Instruction pattern: // this.x = arg0; // super.; // return; private final Instruction[] INITIALIZER_INSTRUCTIONS = new Instruction[] { new VariableInstruction(Instruction.OP_ALOAD_0, 0), new VariableInstruction(Instruction.OP_ALOAD_1, 1), new ConstantInstruction(Instruction.OP_PUTFIELD, 0), new VariableInstruction(Instruction.OP_ALOAD_0, 0), new ConstantInstruction(Instruction.OP_INVOKESPECIAL, InstructionSequenceMatcher.X), new SimpleInstruction(Instruction.OP_RETURN), }; private final InstructionSequenceMatcher INITIALIZER_MATCHER = new InstructionSequenceMatcher(INITIALIZER_CONSTANTS, INITIALIZER_INSTRUCTIONS); // Fields acting as parameters and return values for the visitor methods. private Clazz wrappedClass; private int wrapCounter; // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { if (programClass.subClassCount == 0) { wrappedClass = null; // Can we find one non-static field with a class type? wrapCounter = 0; programClass.fieldsAccept(this); if (wrapCounter == 1) { // Can we find exactly one initializer that initializes this // field? wrapCounter = 0; programClass.methodsAccept(this); if (wrapCounter == 1) { setWrappedClass(programClass, wrappedClass); } } } } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Is the field non-static and of a class type? if ((programField.getAccessFlags() & AccessConstants.STATIC) == 0 && ClassUtil.isInternalClassType(programField.getDescriptor(programClass)) && !ClassUtil.isInternalArrayType(programField.getDescriptor(programClass))) { wrappedClass = programField.referencedClass; if (wrappedClass != null) { wrapCounter++; } else { wrapCounter = Integer.MIN_VALUE; } } else { wrapCounter = Integer.MIN_VALUE; } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Is the method an initializer? if (ClassUtil.isInitializer(programMethod.getName(programClass))) { // Does it have exactly one parameter? if (ClassUtil.internalMethodParameterCount(programMethod.getDescriptor(programClass)) == 1) { // Is the parameter a non-null reference? Value value = StoringInvocationUnit.getMethodParameterValue(programMethod, 1); if (value != null && value.computationalType() == Value.TYPE_REFERENCE && value.referenceValue().isNotNull() == Value.ALWAYS) { // Does the method initialize the field? programMethod.attributesAccept(programClass, this); } else { wrapCounter = Integer.MIN_VALUE; } } else { wrapCounter = Integer.MIN_VALUE; } } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Is the initializer initializing the field? if (codeAttribute.u4codeLength == 10) { INITIALIZER_MATCHER.reset(); codeAttribute.instructionsAccept(clazz, method, INITIALIZER_MATCHER); if (INITIALIZER_MATCHER.isMatching()) { String initializerClassName = clazz.getName(); String fieldClassName = clazz.getClassName(INITIALIZER_MATCHER.matchedConstantIndex(InstructionSequenceMatcher.A)); if (fieldClassName.equals(initializerClassName)) { wrapCounter++; } else { wrapCounter = Integer.MIN_VALUE; } } else { wrapCounter = Integer.MIN_VALUE; } } else { wrapCounter = Integer.MIN_VALUE; } } // Small utility methods. private static void setWrappedClass(Clazz clazz, Clazz wrappedClass) { logger.debug("WrapperClassMarker: [{}] wraps [{}]", clazz.getName(), wrappedClass.getName()); ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setWrappedClass(wrappedClass); } public static Clazz getWrappedClass(Clazz clazz) { return ClassOptimizationInfo.getClassOptimizationInfo(clazz).getWrappedClass(); } } ================================================ FILE: base/src/main/java/proguard/optimize/kotlin/KotlinContextReceiverUsageMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.kotlin; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.kotlin.KotlinClassKindMetadata; import proguard.classfile.kotlin.KotlinConstructorMetadata; import proguard.classfile.kotlin.KotlinDeclarationContainerMetadata; import proguard.classfile.kotlin.KotlinFunctionMetadata; import proguard.classfile.kotlin.KotlinMetadata; import proguard.classfile.kotlin.KotlinPropertyMetadata; import proguard.classfile.kotlin.KotlinTypeMetadata; import proguard.classfile.kotlin.visitor.KotlinConstructorVisitor; import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.KotlinPropertyVisitor; import proguard.optimize.MethodDescriptorShrinker; import proguard.optimize.info.ParameterUsageMarker; import proguard.optimize.info.ProgramMethodOptimizationInfo; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.function.Function; import java.util.stream.IntStream; import static proguard.classfile.AccessConstants.STATIC; import static proguard.optimize.info.ParameterUsageMarker.markParameterUsed; /** * This Kotlin metadata visitor marks the parameters of methods used, * using the {@link ParameterUsageMarker}, if those parameters correspond * to Kotlin context receivers. *

* This is done to ensure that the list of context receivers does not * get out of sync, as otherwise unused context receiver parameters may be removed. *

* On the Java method level, each context receiver is passed as a leading parameter * to a function's JVM method, a property getter/setter method, or a constructor. *

* * context(Foo, Bar) * fun foo(string:String) { } * // -> public static void foo(LFoo;LBar;Ljava/lang/String;)V * *

* TODO(T18173): Implement proper shrinking of context receivers when the underlying * parameter is not used i.e. consistently remove context receivers when the * underlying parameter is removed in e.g. {@link MethodDescriptorShrinker} * * @see ParameterUsageMarker * @see MethodDescriptorShrinker * * @author James Hamilton */ public class KotlinContextReceiverUsageMarker implements KotlinMetadataVisitor, KotlinFunctionVisitor, KotlinConstructorVisitor, KotlinPropertyVisitor { @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) { } @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { kotlinClassKindMetadata.constructorsAccept(clazz, this); } @Override public void visitAnyFunction(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { markContextReceiverParameters(kotlinFunctionMetadata.contextReceivers, kotlinFunctionMetadata.referencedMethod, kotlinFunctionMetadata.referencedDefaultMethod); } @Override public void visitConstructor(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata, KotlinConstructorMetadata kotlinConstructorMetadata) { markContextReceiverParameters(kotlinClassKindMetadata.contextReceivers, kotlinConstructorMetadata.referencedMethod ); } @Override public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata) { markContextReceiverParameters(kotlinPropertyMetadata.contextReceivers, kotlinPropertyMetadata.referencedGetterMethod, kotlinPropertyMetadata.referencedSetterMethod); } private void markContextReceiverParameters(List contextReceivers, Method...methods) { if (contextReceivers == null) { return; } Function isStatic = method -> (method.getAccessFlags() & STATIC) != 0; // Mark all the parameters of the given methods // at each context receiver index as used. IntStream .range(0, contextReceivers.size()) .forEach(paramIndex -> Arrays.stream(methods) .filter(Objects::nonNull) .filter(it -> it.getProcessingInfo() instanceof ProgramMethodOptimizationInfo) .forEachOrdered(it -> markParameterUsed(it, isStatic.apply(it) ? paramIndex : paramIndex + 1))); } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/ClassFinalizer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; import proguard.optimize.KeepMarker; /** * This ClassVisitor makes the program classes that it visits * final, if possible. * * @author Eric Lafortune */ public class ClassFinalizer implements ClassVisitor { private final ClassVisitor extraClassVisitor; /** * Creates a new ClassFinalizer. */ public ClassFinalizer() { this(null); } /** * Creates a new ClassFinalizer. * @param extraClassVisitor an optional extra visitor for all finalized * classes. */ public ClassFinalizer(ClassVisitor extraClassVisitor) { this.extraClassVisitor = extraClassVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // If the class is not final/interface/abstract, // and it is not being kept, // and it doesn't have any subclasses, // then make it final. if ((programClass.u2accessFlags & (AccessConstants.FINAL | AccessConstants.INTERFACE | AccessConstants.ABSTRACT)) == 0 && !KeepMarker.isKept(programClass) && programClass.subClassCount == 0) { programClass.u2accessFlags |= AccessConstants.FINAL; // Visit the class, if required. if (extraClassVisitor != null) { extraClassVisitor.visitProgramClass(programClass); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/ClassMerger.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.visitor.*; import proguard.classfile.editor.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.optimize.KeepMarker; import proguard.optimize.info.*; import proguard.util.*; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; import static proguard.classfile.attribute.Attribute.NEST_HOST; import static proguard.classfile.attribute.Attribute.NEST_MEMBERS; /** * This ClassVisitor inlines the classes that it visits in a given target class, * whenever possible. * * @see RetargetedInnerClassAttributeRemover * @see TargetClassChanger * @see ClassReferenceFixer * @see MemberReferenceFixer * @see AccessFixer * @author Eric Lafortune */ public class ClassMerger implements ClassVisitor, ConstantVisitor { private static final Logger logger = LogManager.getLogger(ClassMerger.class); //* private static final boolean DEBUG = false; private static final boolean DETAILS = false; /*/ private static boolean DEBUG = System.getProperty("cm") != null; private static boolean DETAILS = System.getProperty("cmd") != null; //*/ private final ProgramClass targetClass; private final boolean allowAccessModification; private final boolean mergeInterfacesAggressively; private final boolean mergeWrapperClasses; private final ClassVisitor extraClassVisitor; /** * Creates a new ClassMerger that will merge classes into the given target * class. * @param targetClass the class into which all visited * classes will be merged. * @param allowAccessModification specifies whether the access modifiers * of classes can be changed in order to * merge them. * @param mergeInterfacesAggressively specifies whether interfaces may * be merged aggressively. */ public ClassMerger(ProgramClass targetClass, boolean allowAccessModification, boolean mergeInterfacesAggressively, boolean mergeWrapperClasses) { this(targetClass, allowAccessModification, mergeInterfacesAggressively, mergeWrapperClasses, null); } /** * Creates a new ClassMerger that will merge classes into the given target * class. * @param targetClass the class into which all visited * classes will be merged. * @param allowAccessModification specifies whether the access modifiers * of classes can be changed in order to * merge them. * @param mergeInterfacesAggressively specifies whether interfaces may * be merged aggressively. * @param extraClassVisitor an optional extra visitor for all * merged classes. */ public ClassMerger(ProgramClass targetClass, boolean allowAccessModification, boolean mergeInterfacesAggressively, boolean mergeWrapperClasses, ClassVisitor extraClassVisitor) { this.targetClass = targetClass; this.allowAccessModification = allowAccessModification; this.mergeInterfacesAggressively = mergeInterfacesAggressively; this.mergeWrapperClasses = mergeWrapperClasses; this.extraClassVisitor = extraClassVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Only merge program classes. //final String CLASS_NAME = "abc/Def"; //DEBUG = programClass.getName().equals(CLASS_NAME) || // targetClass.getName().equals(CLASS_NAME); // TODO: Remove this when the class merger has stabilized. // Catch any unexpected exceptions from the actual visiting method. try { visitProgramClass0(programClass); } catch (RuntimeException ex) { logger.error("Unexpected error while merging classes:"); logger.error(" Class = [{}]", programClass.getName()); logger.error(" Target class = [{}]", targetClass.getName()); logger.error(" Exception = [{}] ({})", ex.getClass().getName(), ex.getMessage()); logger.debug("{}", () -> { StringWriter sw = new StringWriter(); programClass.accept(new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); logger.debug("{}", () -> { StringWriter sw = new StringWriter(); targetClass.accept(new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); throw ex; } } private void visitProgramClass0(ProgramClass programClass) { if (isMergeable(programClass)) { // We're not actually merging the classes, but only copying the // contents from the source class to the target class. We'll // then let all other classes point to it. The shrinking step // will finally remove the source class. logger.debug("ClassMerger [{}] -> [{}]", programClass.getName(), targetClass.getName()); logger.debug(" Source instantiated? [ {}]", InstantiationClassMarker.isInstantiated(programClass)); logger.debug(" Target instantiated? [ {}]", InstantiationClassMarker.isInstantiated(targetClass)); logger.debug(" Source interface? [{}]", ((programClass.getAccessFlags() & AccessConstants.INTERFACE)!=0)); logger.debug(" Target interface? [{}]", ((targetClass.getAccessFlags() & AccessConstants.INTERFACE)!=0)); logger.debug(" Source subclasses ({})", programClass.subClassCount); logger.debug(" Target subclasses ({})", targetClass.subClassCount); logger.debug(" Source superclass [{}]", programClass.getSuperClass().getName()); logger.debug(" Target superclass [{}]", targetClass.getSuperClass().getName()); logger.trace("=== Before ==="); logger.trace("{}", () -> { StringWriter sw = new StringWriter(); programClass.accept(new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); logger.trace("{}", () -> { StringWriter sw = new StringWriter(); targetClass.accept(new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); // Combine the access flags. int targetAccessFlags = targetClass.getAccessFlags(); int sourceAccessFlags = programClass.getAccessFlags(); targetClass.u2accessFlags = ((targetAccessFlags & sourceAccessFlags) & (AccessConstants.INTERFACE | AccessConstants.ABSTRACT)) | ((targetAccessFlags | sourceAccessFlags) & (AccessConstants.PUBLIC | AccessConstants.SUPER | AccessConstants.ANNOTATION | AccessConstants.ENUM)); // Copy over the superclass, if it's a non-interface class being // merged into an interface class. // However, we're currently never merging in a way that changes the // superclass. //if ((programClass.getAccessFlags() & AccessConstants.INTERFACE) == 0 && // (targetClass.getAccessFlags() & AccessConstants.INTERFACE) != 0) //{ // targetClass.u2superClass = // new ConstantAdder(targetClass).addConstant(programClass, programClass.u2superClass); //} // Copy over the interfaces that aren't present yet and that // wouldn't cause loops in the class hierarchy. // Note that the code shouldn't be iterating over the original // list at this point. programClass.interfaceConstantsAccept( new ExceptClassConstantFilter(targetClass.getName(), new ImplementedClassConstantFilter(targetClass, new ImplementingClassConstantFilter(targetClass, new InterfaceAdder(targetClass))))); // Make sure that the interfaces of the target class have the // target class as subclass. We'll have to clean up the // subclasses further when we actually apply the targets. targetClass.interfaceConstantsAccept( new ReferencedClassVisitor( new SubclassFilter(targetClass, new SubclassAdder(targetClass)))); // Create a visitor to copy class members. MemberVisitor memberAdder = new MemberAdder(targetClass, new MultiMemberVisitor( // Copy or link optimization info. new MyMemberOptimizationInfoCopier(), // Mark copied members as being modified. new ProcessingFlagSetter(ProcessingFlags.MODIFIED) )); // Copy over the fields (only static from wrapper classes). programClass.fieldsAccept(mergeWrapperClasses ? new MemberAccessFilter( AccessConstants.STATIC, 0, memberAdder) : memberAdder); // Copy over the methods (not initializers from wrapper classes). programClass.methodsAccept(mergeWrapperClasses ? new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.METHOD_NAME_INIT)), memberAdder) : memberAdder); // Copy over the other attributes. programClass.attributesAccept( new AttributeNameFilter(new NotMatcher( new OrMatcher(new FixedStringMatcher(Attribute.BOOTSTRAP_METHODS), new OrMatcher(new FixedStringMatcher(Attribute.SOURCE_FILE), new OrMatcher(new FixedStringMatcher(Attribute.INNER_CLASSES), new FixedStringMatcher(Attribute.ENCLOSING_METHOD))))), new AttributeAdder(targetClass, true))); // Update the optimization information of the target class. logger.debug("merging optimization info for {} and {}", targetClass.getName(), programClass.getName()); ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(targetClass) .merge(ClassOptimizationInfo.getClassOptimizationInfo(programClass)); // Remember to replace the inlined class by the target class. setTargetClass(programClass, targetClass); logger.trace("=== After ===="); logger.trace("Target instantiated? {}", InstantiationClassMarker.isInstantiated(targetClass)); logger.trace("{}", () -> { StringWriter sw = new StringWriter(); targetClass.accept(new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); logger.trace(" Interfaces:"); logger.trace("{}", () -> { StringWriter sw = new StringWriter(); targetClass.interfaceConstantsAccept(new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); // Visit the merged class, if required. if (extraClassVisitor != null) { extraClassVisitor.visitProgramClass(programClass); } } } public boolean isMergeable(ProgramClass programClass) { return !programClass.equals(targetClass) && (!DETAILS || print(programClass, "isKept?")) && // Don't merge classes that must be preserved. !KeepMarker.isKept(programClass) && !KeepMarker.isKept(targetClass) && (!DETAILS || print(programClass, "already retargeted?")) && // Only merge classes that haven't been retargeted yet. getTargetClass(programClass) == null && getTargetClass(targetClass) == null && (!DETAILS || print(programClass, "isAnnotation?")) && // Don't merge annotation classes, with all their reflection and // infinite recursion. (programClass.getAccessFlags() & AccessConstants.ANNOTATION) == 0 && (!DETAILS || print(programClass, "Version?")) && // Only merge classes with equal class versions. programClass.u4version == targetClass.u4version && (!DETAILS || print(programClass, "Nest host or member?")) && // Don't merge nest hosts or members. !isNestHostOrMember(programClass) && (!DETAILS || print(programClass, "No non-copiable attributes?")) && // The class to be merged into the target class must not have // non-copiable attributes (InnerClass, EnclosingMethod), // unless it is a synthetic class. (mergeWrapperClasses || (programClass.getAccessFlags() & AccessConstants.SYNTHETIC) != 0 || !hasNonCopiableAttributes(programClass)) && (!DETAILS || print(programClass, "Package visibility?")) && // Only merge classes if we can change the access permissions, or // if they are in the same package, or // if they are public and don't contain or invoke package visible // class members. (allowAccessModification || ((programClass.getAccessFlags() & targetClass.getAccessFlags() & AccessConstants.PUBLIC) != 0 && !PackageVisibleMemberContainingClassMarker.containsPackageVisibleMembers(programClass) && !PackageVisibleMemberInvokingClassMarker.invokesPackageVisibleMembers(programClass)) || ClassUtil.internalPackageName(programClass.getName()).equals( ClassUtil.internalPackageName(targetClass.getName()))) && (!DETAILS || print(programClass, "Interface/abstract/single?")) && // Only merge two classes or two interfaces or two abstract classes, // or a single implementation into its interface. ((programClass.getAccessFlags() & (AccessConstants.INTERFACE | AccessConstants.ABSTRACT)) == (targetClass.getAccessFlags() & (AccessConstants.INTERFACE | AccessConstants.ABSTRACT)) || (isOnlySubClass(programClass, targetClass) && programClass.getSuperClass() != null && (programClass.getSuperClass().equals(targetClass) || programClass.getSuperClass().equals(targetClass.getSuperClass())))) && (!DETAILS || print(programClass, "Indirect implementation?")) && // One class must not implement the other class indirectly. !indirectlyImplementedInterfaces(programClass).contains(targetClass) && !targetClass.extendsOrImplements(programClass) && (!DETAILS || print(programClass, "Interfaces same subinterfaces?")) && // Interfaces must have exactly the same subinterfaces, not // counting themselves, to avoid any loops in the interface // hierarchy. ((programClass.getAccessFlags() & AccessConstants.INTERFACE) == 0 || (targetClass.getAccessFlags() & AccessConstants.INTERFACE) == 0 || subInterfaces(programClass, targetClass).equals(subInterfaces(targetClass, programClass))) && (!DETAILS || print(programClass, "Same initialized superclasses?")) && // The two classes must have the same superclasses and interfaces // with static initializers. sideEffectSuperClasses(programClass).equals(sideEffectSuperClasses(targetClass)) && (!DETAILS || print(programClass, "Same instanceofed superclasses?")) && // The two classes must have the same superclasses and interfaces // that are tested with 'instanceof'. instanceofedSuperClasses(programClass).equals(instanceofedSuperClasses(targetClass)) && (!DETAILS || print(programClass, "Same caught superclasses?")) && // The two classes must have the same superclasses that are caught // as exceptions. caughtSuperClasses(programClass).equals(caughtSuperClasses(targetClass)) && (!DETAILS || print(programClass, "Not .classed?")) && // The two classes must not both be part of a .class construct. !(DotClassMarker.isDotClassed(programClass) && DotClassMarker.isDotClassed(targetClass)) && (!DETAILS || print(programClass, "No clashing fields?")) && // The classes must not have clashing fields. (mergeWrapperClasses || !haveAnyIdenticalFields(programClass, targetClass)) && (!DETAILS || print(programClass, "No unwanted fields?")) && // The two classes must not introduce any unwanted fields. (mergeWrapperClasses || !introducesUnwantedFields(programClass, targetClass) && !introducesUnwantedFields(targetClass, programClass)) && (!DETAILS || print(programClass, "No shadowed fields?")) && // The two classes must not shadow each others fields. (mergeWrapperClasses || !shadowsAnyFields(programClass, targetClass) && !shadowsAnyFields(targetClass, programClass)) && (!DETAILS || print(programClass, "No clashing methods?")) && // The classes must not have clashing methods. !haveAnyIdenticalMethods(programClass, targetClass) && (!DETAILS || print(programClass, "No abstract methods?")) && // The classes must not introduce abstract methods, unless // explicitly allowed. (mergeInterfacesAggressively || (!introducesUnwantedAbstractMethods(programClass, targetClass) && !introducesUnwantedAbstractMethods(targetClass, programClass))) && (!DETAILS || print(programClass, "No native methods?")) && // No native methods in the program class that is getting inlined. noNativeMethodIn(programClass) && (!DETAILS || print(programClass, "No overridden methods?")) && // The classes must not override each others concrete methods. !overridesAnyMethods(programClass, targetClass) && !overridesAnyMethods(targetClass, programClass) && (!DETAILS || print(programClass, "No shadowed methods?")) && // The classes must not shadow each others non-private methods. !shadowsAnyMethods(programClass, targetClass) && !shadowsAnyMethods(targetClass, programClass) && (!DETAILS || print(programClass, "No type variables/parameterized types?")) && // The two classes must not have a signature attribute as type variables // and/or parameterized types can not always be merged. !hasSignatureAttribute(programClass) && !hasSignatureAttribute(targetClass); } private boolean noNativeMethodIn(ProgramClass programClass) { MemberCounter counter = new MemberCounter(); programClass.methodsAccept(new MemberAccessFilter(AccessConstants.NATIVE, 0, counter)); return counter.getCount() == 0; } private boolean print(ProgramClass programClass, String message) { logger.debug("Merge [{}] <- [{}] {}", targetClass.getName(), programClass.getName(), message); return true; } // Small utility methods. /** * Returns whether a given class is the only subclass of another given class. */ private boolean isOnlySubClass(Clazz subClass, ProgramClass clazz) { return clazz.subClassCount == 1 && clazz.subClasses[0].equals(subClass); } /** * Returns the set of indirectly implemented interfaces. */ private Set indirectlyImplementedInterfaces(Clazz clazz) { Set set = new HashSet(); ReferencedClassVisitor referencedInterfaceCollector = new ReferencedClassVisitor( new ClassHierarchyTraveler(false, false, true, false, new ClassCollector(set))); // Visit all superclasses and collect their interfaces. clazz.superClassConstantAccept(referencedInterfaceCollector); // Visit all interfaces and collect their interfaces. clazz.interfaceConstantsAccept(referencedInterfaceCollector); return set; } /** * Returns the set of interface subclasses, not including the given class. */ private Set subInterfaces(Clazz clazz, Clazz exceptClass) { Set set = new HashSet(); // Visit all subclasses, collecting the interface classes. clazz.hierarchyAccept(false, false, false, true, new ClassAccessFilter(AccessConstants.INTERFACE, 0, new ExceptClassesFilter(new Clazz[] { exceptClass }, new ClassCollector(set)))); return set; } /** * Returns the set of superclasses and interfaces that are initialized. */ private Set sideEffectSuperClasses(Clazz clazz) { Set set = new HashSet(); // Visit all superclasses and interfaces, collecting the ones that have // static initializers. clazz.hierarchyAccept(true, true, true, false, new SideEffectClassFilter( new ClassCollector(set))); return set; } /** * Returns the set of superclasses and interfaces that are used in * 'instanceof' tests. */ private Set instanceofedSuperClasses(Clazz clazz) { Set set = new HashSet(); // Visit all superclasses and interfaces, collecting the ones that are // used in an 'instanceof' test. clazz.hierarchyAccept(true, true, true, false, new InstanceofClassFilter( new ClassCollector(set))); return set; } /** * Returns the set of superclasses that are caught as exceptions. */ private Set caughtSuperClasses(Clazz clazz) { // Don't bother if this isn't an exception at all. if (!clazz.extends_(ClassConstants.NAME_JAVA_LANG_THROWABLE)) { return Collections.EMPTY_SET; } // Visit all superclasses, collecting the ones that are caught // (plus java.lang.Object, in the current implementation). Set set = new HashSet(); clazz.hierarchyAccept(true, true, false, false, new CaughtClassFilter( new ClassCollector(set))); return set; } /** * Returns whether the given class has a Signature attributes containing * type variables or parameterized types. */ static boolean hasSignatureAttribute(Clazz clazz) { AttributeCounter counter = new AttributeCounter(); clazz.attributesAccept( new AttributeNameFilter( new FixedStringMatcher(Attribute.SIGNATURE), counter)); return counter.getCount() > 0; } /** * Returns whether the two given classes have fields with the same * names and descriptors. */ private boolean haveAnyIdenticalFields(Clazz clazz, Clazz targetClass) { MemberCounter counter = new MemberCounter(); // Visit all fields, counting the with the same name and descriptor in // the target class. clazz.fieldsAccept(new SimilarMemberVisitor(targetClass, true, false, false, false, counter)); return counter.getCount() > 0; } /** * Returns whether the given class would introduce any unwanted fields * in the target class. */ private boolean introducesUnwantedFields(Clazz programClass, ProgramClass targetClass) { // It's ok if the target class is never instantiated and does not // have any subclasses except for maybe the source class. if (!InstantiationClassMarker.isInstantiated(targetClass) && (targetClass.subClassCount == 0 || isOnlySubClass(programClass, targetClass))) { return false; } MemberCounter counter = new MemberCounter(); // Count all non-static fields in the the source class. programClass.fieldsAccept(new MemberAccessFilter(0, AccessConstants.STATIC, counter)); return counter.getCount() > 0; } /** * Returns whether the given class or its subclasses shadow any fields in * the given target class. */ private boolean shadowsAnyFields(Clazz clazz, Clazz targetClass) { MemberCounter counter = new MemberCounter(); // Visit all fields, counting the ones that are shadowing non-private // fields in the class hierarchy of the target class. clazz.hierarchyAccept(true, false, false, true, new AllFieldVisitor( new SimilarMemberVisitor(targetClass, true, true, true, false, new MemberAccessFilter(0, AccessConstants.PRIVATE, counter)))); return counter.getCount() > 0; } /** * Returns whether the two given classes have class members with the same * name and descriptor. */ private boolean haveAnyIdenticalMethods(Clazz clazz, Clazz targetClass) { MemberCounter counter = new MemberCounter(); // Visit all non-abstract methods, counting the ones that are also // present in the target class. clazz.methodsAccept(new MemberAccessFilter(0, AccessConstants.ABSTRACT, new SimilarMemberVisitor(targetClass, true, false, false, false, new MemberAccessFilter(0, AccessConstants.ABSTRACT, counter)))); return counter.getCount() > 0; } /** * Returns whether the given class would introduce any abstract methods * in the target class. */ private boolean introducesUnwantedAbstractMethods(Clazz clazz, ProgramClass targetClass) { // It's ok if the target class is already abstract and does not // have any subclasses except for maybe the source class. if ((targetClass.getAccessFlags() & (AccessConstants.ABSTRACT | AccessConstants.INTERFACE)) != 0 && (targetClass.subClassCount == 0 || isOnlySubClass(clazz, targetClass))) { return false; } MemberCounter counter = new MemberCounter(); Set targetSet = new HashSet(); // Collect all abstract methods, and similar abstract methods in the // class hierarchy of the target class. clazz.methodsAccept(new MemberAccessFilter(AccessConstants.ABSTRACT, 0, new MultiMemberVisitor( counter, new SimilarMemberVisitor(targetClass, true, true, true, false, new MemberAccessFilter(AccessConstants.ABSTRACT, 0, new MemberCollector(false, true, true, targetSet))) ))); return targetSet.size() < counter.getCount(); } /** * Returns whether the given class overrides any methods in the given * target class. */ private boolean overridesAnyMethods(Clazz clazz, ProgramClass targetClass) { // It's ok if the target class is never instantiated and does // not have any subclasses except for maybe the source class. if (!InstantiationClassMarker.isInstantiated(targetClass) && (targetClass.subClassCount == 0 || isOnlySubClass(clazz, targetClass))) { return false; } MemberCounter counter = new MemberCounter(); // Visit all non-abstract methods, counting the ones that are // overriding methods in the class hierarchy of the target class. clazz.methodsAccept(new MemberAccessFilter(0, AccessConstants.ABSTRACT, new InitializerMethodFilter(null, new SimilarMemberVisitor(targetClass, true, true, false, false, new MemberAccessFilter(0, AccessConstants.PRIVATE | AccessConstants.STATIC | AccessConstants.ABSTRACT, counter))))); return counter.getCount() > 0; } /** * Returns whether the given class or its subclasses have private or * static methods that shadow any methods in the given target class. */ private boolean shadowsAnyMethods(Clazz clazz, Clazz targetClass) { // It's ok if the source class already extends the target class // or (in practice) vice versa. if (clazz.extends_(targetClass) || targetClass.extends_(clazz)) { return false; } MemberCounter counter = new MemberCounter(); // Visit all methods, counting the ones that are shadowing // final methods in the class hierarchy of the target class. clazz.hierarchyAccept(true, false, false, true, new AllMethodVisitor( new InitializerMethodFilter(null, new SimilarMemberVisitor(targetClass, true, true, false, false, new MemberAccessFilter(AccessConstants.FINAL, 0, counter))))); if (counter.getCount() > 0) { return true; } // Visit all private methods, counting the ones that are shadowing // non-private methods in the class hierarchy of the target class. clazz.hierarchyAccept(true, false, false, true, new AllMethodVisitor( new MemberAccessFilter(AccessConstants.PRIVATE, 0, new InitializerMethodFilter(null, new SimilarMemberVisitor(targetClass, true, true, true, false, new MemberAccessFilter(0, AccessConstants.PRIVATE, counter)))))); if (counter.getCount() > 0) { return true; } // Visit all static methods, counting the ones that are shadowing // non-private methods in the class hierarchy of the target class. clazz.hierarchyAccept(true, false, false, true, new AllMethodVisitor( new MemberAccessFilter(AccessConstants.STATIC, 0, new InitializerMethodFilter(null, new SimilarMemberVisitor(targetClass, true, true, true, false, new MemberAccessFilter(0, AccessConstants.PRIVATE, counter)))))); return counter.getCount() > 0; } /** * Returns whether the given class has any attributes that can not be copied when * merging it into another class. */ private boolean hasNonCopiableAttributes(Clazz clazz) { AttributeCounter counter = new AttributeCounter(); // Copy over the other attributes. clazz.attributesAccept( new AttributeNameFilter( new OrMatcher(new FixedStringMatcher(Attribute.INNER_CLASSES), new FixedStringMatcher(Attribute.ENCLOSING_METHOD)), counter)); return counter.getCount() > 0; } public static boolean isNestHostOrMember(Clazz clazz) { AttributeCounter counter = new AttributeCounter(); clazz.attributesAccept( new AttributeNameFilter( new OrMatcher(new FixedStringMatcher(Attribute.NEST_HOST), new FixedStringMatcher(Attribute.NEST_MEMBERS)), counter)); return counter.getCount() > 0; } public static void setTargetClass(Clazz clazz, Clazz targetClass) { ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setTargetClass(targetClass); } public static Clazz getTargetClass(Clazz clazz) { Clazz targetClass = null; // Return the last target class, if any. while (true) { clazz = ClassOptimizationInfo.getClassOptimizationInfo(clazz).getTargetClass(); if (clazz == null) { return targetClass; } targetClass = clazz; } } /** * This MemberVisitor copies field optimization info from copied fields. */ private static class MyMemberOptimizationInfoCopier implements MemberVisitor { public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Copy the optimization info from the field that was just copied. ProgramField copiedField = (ProgramField)programField.getProcessingInfo(); Object info = copiedField.getProcessingInfo(); programField.setProcessingInfo(info instanceof ProgramFieldOptimizationInfo ? new ProgramFieldOptimizationInfo((ProgramFieldOptimizationInfo)info) : info); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Linked methods share their optimization info. } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/GotoCommonCodeReplacer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.BranchTargetFinder; /** * This AttributeVisitor redirects unconditional branches so any common code * is shared, and the code preceding the branch can be removed, in the code * attributes that it visits. * * @author Eric Lafortune */ public class GotoCommonCodeReplacer implements AttributeVisitor, InstructionVisitor { private static final Logger logger = LogManager.getLogger(GotoCommonCodeReplacer.class); private final InstructionVisitor extraInstructionVisitor; private final BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); /** * Creates a new GotoCommonCodeReplacer. * @param extraInstructionVisitor an optional extra visitor for all replaced * goto instructions. */ public GotoCommonCodeReplacer(InstructionVisitor extraInstructionVisitor) { this.extraInstructionVisitor = extraInstructionVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // DEBUG = // clazz.getName().equals("abc/Def") && // method.getName(clazz).equals("abc"); // Mark all branch targets. branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute); // Reset the code attribute editor. codeAttributeEditor.reset(codeAttribute.u4codeLength); // Remap the variables of the instructions. codeAttribute.instructionsAccept(clazz, method, this); // Apply the code attribute editor. codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { // Check if the instruction is an unconditional goto instruction that // isn't the target of a branch itself. byte opcode = branchInstruction.opcode; if ((opcode == Instruction.OP_GOTO || opcode == Instruction.OP_GOTO_W) && !branchTargetFinder.isBranchTarget(offset)) { int branchOffset = branchInstruction.branchOffset; int targetOffset = offset + branchOffset; // Get the number of common bytes. int commonCount = commonByteCodeCount(codeAttribute, offset, targetOffset); if (commonCount > 0 && !exceptionBoundary(codeAttribute, offset, targetOffset)) { logger.debug("GotoCommonCodeReplacer: {}.{}{} ([{}] - {} -> {})", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), offset-commonCount, branchInstruction.toString(offset), targetOffset ); // Delete the common instructions. for (int delta = 0; delta <= commonCount; delta++) { int deleteOffset = offset - delta; if (branchTargetFinder.isInstruction(deleteOffset)) { codeAttributeEditor.clearModifications(deleteOffset); codeAttributeEditor.deleteInstruction(deleteOffset); } } // Redirect the goto instruction, if it is still necessary. int newBranchOffset = branchOffset - commonCount; if (newBranchOffset != branchInstruction.length(offset)) { Instruction newGotoInstruction = new BranchInstruction(opcode, newBranchOffset); codeAttributeEditor.replaceInstruction(offset, newGotoInstruction); } // Visit the instruction, if required. if (extraInstructionVisitor != null) { extraInstructionVisitor.visitBranchInstruction(clazz, method, codeAttribute, offset, branchInstruction); } } } } // Small utility methods. /** * Returns the number of common bytes preceding the given offsets, * avoiding branches and exception blocks. */ private int commonByteCodeCount(CodeAttribute codeAttribute, int offset1, int offset2) { // Find the block of common instructions preceding it. byte[] code = codeAttribute.code; int successfulDelta = 0; for (int delta = 1; delta <= offset1 && delta <= offset2 && offset2 - delta != offset1; delta++) { int newOffset1 = offset1 - delta; int newOffset2 = offset2 - delta; // Is the code identical at both offsets? if (code[newOffset1] != code[newOffset2]) { break; } // Are there instructions at either offset but not both? if (branchTargetFinder.isInstruction(newOffset1) ^ branchTargetFinder.isInstruction(newOffset2)) { break; } // Are there instructions at both offsets? if (branchTargetFinder.isInstruction(newOffset1) && branchTargetFinder.isInstruction(newOffset2)) { // Are the offsets involved in some branches? // Note that the preverifier doesn't like initializer // invocations to be moved around. // Also note that the preverifier doesn't like pop instructions // that work on different operands. if (branchTargetFinder.isBranchOrigin(newOffset1) || branchTargetFinder.isBranchTarget(newOffset1) || branchTargetFinder.isExceptionStart(newOffset1) || branchTargetFinder.isExceptionEnd(newOffset1) || branchTargetFinder.isInitializer(newOffset1) || branchTargetFinder.isExceptionStart(newOffset2) || branchTargetFinder.isExceptionEnd(newOffset2) || isPop(code[newOffset1])) { break; } // Make sure the new branch target was a branch target before, // in order not to introduce new entries in the stack map table. if (branchTargetFinder.isBranchTarget(newOffset2)) { successfulDelta = delta; } if (branchTargetFinder.isBranchTarget(newOffset1)) { break; } } } return successfulDelta; } /** * Returns whether the given opcode represents a pop instruction that must * get a consistent type (pop, pop2, arraylength). */ private boolean isPop(byte opcode) { return opcode == Instruction.OP_POP || opcode == Instruction.OP_POP2 || opcode == Instruction.OP_ARRAYLENGTH; } /** * Returns the whether there is a boundary of an exception block between * the given offsets (including both). */ private boolean exceptionBoundary(CodeAttribute codeAttribute, int offset1, int offset2) { // Swap the offsets if the second one is smaller than the first one. if (offset2 < offset1) { int offset = offset1; offset1 = offset2; offset2 = offset; } // Check if there is a boundary of an exception block. for (int offset = offset1; offset <= offset2; offset++) { if (branchTargetFinder.isExceptionStart(offset) || branchTargetFinder.isExceptionEnd(offset)) { return true; } } return false; } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/GotoGotoReplacer.java ================================================ /* * 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.peephole; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This InstructionVisitor simplifies unconditional branches to other * unconditional branches. * * @author Eric Lafortune */ public class GotoGotoReplacer implements InstructionVisitor { private final CodeAttributeEditor codeAttributeEditor; private final InstructionVisitor extraInstructionVisitor; /** * Creates a new GotoGotoReplacer. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. */ public GotoGotoReplacer(CodeAttributeEditor codeAttributeEditor) { this(codeAttributeEditor, null); } /** * Creates a new GotoGotoReplacer. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. * @param extraInstructionVisitor an optional extra visitor for all replaced * goto instructions. */ public GotoGotoReplacer(CodeAttributeEditor codeAttributeEditor, InstructionVisitor extraInstructionVisitor) { this.codeAttributeEditor = codeAttributeEditor; this.extraInstructionVisitor = extraInstructionVisitor; } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { // Check if the instruction is an unconditional goto instruction. byte opcode = branchInstruction.opcode; if (opcode == Instruction.OP_GOTO || opcode == Instruction.OP_GOTO_W) { // Check if the goto instruction points to another simple goto // instruction. int branchOffset = branchInstruction.branchOffset; int targetOffset = offset + branchOffset; if (branchOffset != 0 && branchOffset != branchInstruction.length(offset) && !codeAttributeEditor.isModified(offset) && !codeAttributeEditor.isModified(targetOffset)) { Instruction targetInstruction = InstructionFactory.create(codeAttribute.code, targetOffset); if (targetInstruction.opcode == Instruction.OP_GOTO) { // Simplify the goto instruction. int targetBranchOffset = ((BranchInstruction)targetInstruction).branchOffset; Instruction newBranchInstruction = new BranchInstruction(opcode, (branchOffset + targetBranchOffset)); codeAttributeEditor.replaceInstruction(offset, newBranchInstruction); // Visit the instruction, if required. if (extraInstructionVisitor != null) { extraInstructionVisitor.visitBranchInstruction(clazz, method, codeAttribute, offset, branchInstruction); } } } } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/GotoReturnReplacer.java ================================================ /* * 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.peephole; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This InstructionVisitor replaces unconditional branches to return instructions * by these same return instructions. * * @author Eric Lafortune */ public class GotoReturnReplacer implements InstructionVisitor { private final CodeAttributeEditor codeAttributeEditor; private final InstructionVisitor extraInstructionVisitor; /** * Creates a new GotoReturnReplacer. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. */ public GotoReturnReplacer(CodeAttributeEditor codeAttributeEditor) { this(codeAttributeEditor, null); } /** * Creates a new GotoReturnReplacer. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. * @param extraInstructionVisitor an optional extra visitor for all replaced * goto instructions. */ public GotoReturnReplacer(CodeAttributeEditor codeAttributeEditor, InstructionVisitor extraInstructionVisitor) { this.codeAttributeEditor = codeAttributeEditor; this.extraInstructionVisitor = extraInstructionVisitor; } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { // Check if the instruction is an unconditional goto instruction. byte opcode = branchInstruction.opcode; if (opcode == Instruction.OP_GOTO || opcode == Instruction.OP_GOTO_W) { // Check if the goto instruction points to a return instruction. int targetOffset = offset + branchInstruction.branchOffset; if (!codeAttributeEditor.isModified(offset) && !codeAttributeEditor.isModified(targetOffset)) { Instruction targetInstruction = InstructionFactory.create(codeAttribute.code, targetOffset); switch (targetInstruction.opcode) { case Instruction.OP_IRETURN: case Instruction.OP_LRETURN: case Instruction.OP_FRETURN: case Instruction.OP_DRETURN: case Instruction.OP_ARETURN: case Instruction.OP_RETURN: // Replace the goto instruction by the return instruction. Instruction returnInstruction = new SimpleInstruction(targetInstruction.opcode); codeAttributeEditor.replaceInstruction(offset, returnInstruction); // Visit the instruction, if required. if (extraInstructionVisitor != null) { extraInstructionVisitor.visitBranchInstruction(clazz, method, codeAttribute, offset, branchInstruction); } break; } } } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/HorizontalClassMerger.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import proguard.classfile.*; import proguard.classfile.visitor.*; import proguard.optimize.KeepMarker; import proguard.optimize.info.*; import proguard.util.*; import java.util.*; import java.util.stream.*; /** * This ClassPoolVisitor inlines siblings in the program classes that it visits, whenever possible. * * @author Eric Lafortune * @see ClassMerger */ public class HorizontalClassMerger implements ClassPoolVisitor { private static final int NOT_INSTANTIATED_NO_MEMBERS = 3; private static final int NOT_INSTANTIATED_WITH_MEMBERS = 2; private static final int INSTANTIATED_NO_MEMBERS = 1; private static final int INSTANTIATED_WITH_MEMBERS = 0; private final boolean allowAccessModification; private final boolean mergeInterfacesAggressively; private final ClassVisitor extraClassVisitor; private final Set forbiddenClassNames; /** * Creates a new HorizontalClassMerger. * @param allowAccessModification specifies whether the access modifiers of classes can be changed in order to * merge them. * @param mergeInterfacesAggressively specifies whether interfaces may be merged aggressively. * @param forbiddenClassNames specifies the names of classes which are excluded from the chance of being merged */ public HorizontalClassMerger(boolean allowAccessModification, boolean mergeInterfacesAggressively, Set forbiddenClassNames) { this(allowAccessModification, mergeInterfacesAggressively, forbiddenClassNames, null); } /** * Creates a new HorizontalClassMerger. * @param allowAccessModification specifies whether the access modifiers of classes can be changed in order to * merge them. * @param mergeInterfacesAggressively specifies whether interfaces may be merged aggressively. * @param forbiddenClassNames specifies the names of classes which are excluded from the chance of being merged * @param extraClassVisitor an optional extra visitor for all merged classes. */ public HorizontalClassMerger(boolean allowAccessModification, boolean mergeInterfacesAggressively, Set forbiddenClassNames, ClassVisitor extraClassVisitor) { this.allowAccessModification = allowAccessModification; this.mergeInterfacesAggressively = mergeInterfacesAggressively; this.forbiddenClassNames = forbiddenClassNames; this.extraClassVisitor = extraClassVisitor; } // Implementations for ClassPoolVisitor. public void visitClassPool(ClassPool classPool) { // 1. Make a stream of classes in the classPool // 2. Filter out all classes with no chance of success // 3. Partition the remaining classes into sets of siblings Map> siblingsCollections = StreamSupport.stream(classPool.classes().spliterator(), false) .filter(this::isCandidateForMerging) .collect(Collectors.groupingBy(Clazz::getSuperClass)); // 4. Try horizontal merging for each set of siblings siblingsCollections.values().forEach(this::handleSiblings); } // Utility Methods /** * This method tries to merge a set of siblings, but preclassifies them so that the introducesUnwantedFields * constraint of the class merger is certainly fullfilled. */ private void handleSiblings(List classes) { Map> partition = classes.stream().collect(Collectors.groupingBy(HorizontalClassMerger::classify)); List notInstantiatedNoMembers = partition.getOrDefault(NOT_INSTANTIATED_NO_MEMBERS, Collections.emptyList()); List notInstantiatedWithMembers = partition.getOrDefault(NOT_INSTANTIATED_WITH_MEMBERS, Collections.emptyList()); List instantiatedNoMembers = partition.getOrDefault(INSTANTIATED_NO_MEMBERS, Collections.emptyList()); // The conditions for merging are almost symmetric, the extra constraints are put onto the // source of the merging, so we opt to put the largest set as the first set. mergeInto(classes, notInstantiatedNoMembers); mergeInto(notInstantiatedWithMembers, notInstantiatedWithMembers); mergeInto(instantiatedNoMembers, instantiatedNoMembers); } /** * Tries to merge all sourceClasses into the targetClasses * @param sourceClasses * @param targetClasses */ private void mergeInto(List sourceClasses, List targetClasses) { for (Clazz target : targetClasses) { ClassMerger classMerger = new ClassMerger((ProgramClass)target, allowAccessModification, mergeInterfacesAggressively, false, extraClassVisitor); for (Clazz source : sourceClasses) { source.accept(classMerger); } } } /** * Checks if a class can trivially not be used for merging (either as target or source) * @param clazz the class. * @return true if the give clazz is a candidate for merging. */ public boolean isCandidateForMerging(Clazz clazz) { return !( KeepMarker.isKept(clazz) || ClassMerger.getTargetClass(clazz) != null || clazz instanceof LibraryClass || ClassMerger.hasSignatureAttribute(clazz) || DotClassMarker.isDotClassed(clazz) || (clazz.getAccessFlags() & AccessConstants.ANNOTATION) != 0 || (clazz.getProcessingFlags() & ProcessingFlags.INJECTED) != 0 || forbiddenClassNames.contains(clazz.getName()) || clazz.getSuperClass() == null); } /** * Returns an integer that classifies a class into four types: * 0: This class is instantiated and contains non-static members * 1: This class is not instantiated and contains non-static members. * 2: This class is instantiated and contains no non-static members * 3: This class is not instantiated and contains no non-static members */ private static Integer classify(Clazz clazz) { boolean isInstantiated = InstantiationClassMarker.isInstantiated(clazz) || (((ProgramClass)clazz).subClassCount > 0); MemberCounter counter = new MemberCounter(); // Count all non-static fields in the the source class. clazz.fieldsAccept(new MemberAccessFilter(0, AccessConstants.STATIC, counter)); boolean hasMembers = counter.getCount() > 0; if (isInstantiated) { if (hasMembers) { return INSTANTIATED_WITH_MEMBERS; } else { return INSTANTIATED_NO_MEMBERS; } } else { if (hasMembers) { return NOT_INSTANTIATED_WITH_MEMBERS; } else { return NOT_INSTANTIATED_NO_MEMBERS; } } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/InstructionSequenceConstants.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.editor.*; import proguard.classfile.instruction.Instruction; import proguard.classfile.visitor.ClassPrinter; /** * This class contains a set of instruction sequences with their suggested * more compact or more efficient replacements. * * @see InstructionSequencesReplacer * @see InstructionSequenceReplacer * @author Eric Lafortune */ public class InstructionSequenceConstants { // The arrays with constants and instructions used to be static, // but now they are initialized with references to classes and // class members, inside an instance of this class. As an added // benefit, they can be garbage collected after they have been used. public final Instruction[][][] VARIABLE_SEQUENCES; public final Instruction[][][] ARITHMETIC_SEQUENCES; public final Instruction[][][] FIELD_SEQUENCES; public final Instruction[][][] CAST_SEQUENCES; public final Instruction[][][] BRANCH_SEQUENCES; public final Instruction[][][] STRING_SEQUENCES; public final Instruction[][][] OBJECT_SEQUENCES; public final Instruction[][][] MATH_SEQUENCES; public final Instruction[][][] MATH_ANDROID_SEQUENCES; public final Constant[] CONSTANTS; // Internal short-hand constants. private static final String BOOLEAN = ClassConstants.NAME_JAVA_LANG_BOOLEAN; private static final String BYTE = ClassConstants.NAME_JAVA_LANG_BYTE; private static final String CHARACTER = ClassConstants.NAME_JAVA_LANG_CHARACTER; private static final String SHORT = ClassConstants.NAME_JAVA_LANG_SHORT; private static final String INTEGER = ClassConstants.NAME_JAVA_LANG_INTEGER; private static final String LONG = ClassConstants.NAME_JAVA_LANG_LONG; private static final String FLOAT = ClassConstants.NAME_JAVA_LANG_FLOAT; private static final String DOUBLE = ClassConstants.NAME_JAVA_LANG_DOUBLE; private static final String CLASS = ClassConstants.NAME_JAVA_LANG_CLASS; private static final String STRING = ClassConstants.NAME_JAVA_LANG_STRING; private static final String STRING_BUFFER = ClassConstants.NAME_JAVA_LANG_STRING_BUFFER; private static final String STRING_BUILDER = ClassConstants.NAME_JAVA_LANG_STRING_BUILDER; private static final String MATH = ClassConstants.NAME_JAVA_LANG_MATH; private static final String FLOAT_MATH = ClassConstants.NAME_ANDROID_UTIL_FLOAT_MATH; private static final int X = InstructionSequenceReplacer.X; private static final int Y = InstructionSequenceReplacer.Y; private static final int Z = InstructionSequenceReplacer.Z; private static final int A = InstructionSequenceReplacer.A; private static final int B = InstructionSequenceReplacer.B; private static final int C = InstructionSequenceReplacer.C; private static final int D = InstructionSequenceReplacer.D; // Replacement constants that are derived from matched variables. private static final int STRING_A_LENGTH = InstructionSequenceReplacer.STRING_A_LENGTH; private static final int CLASS_A_NAME = InstructionSequenceReplacer.CLASS_A_NAME; private static final int CLASS_A_SIMPLE_NAME = InstructionSequenceReplacer.CLASS_A_SIMPLE_NAME; private static final int BOOLEAN_A_STRING = InstructionSequenceReplacer.BOOLEAN_A_STRING; private static final int CHAR_A_STRING = InstructionSequenceReplacer.CHAR_A_STRING; private static final int INT_A_STRING = InstructionSequenceReplacer.INT_A_STRING; private static final int LONG_A_STRING = InstructionSequenceReplacer.LONG_A_STRING; private static final int FLOAT_A_STRING = InstructionSequenceReplacer.FLOAT_A_STRING; private static final int DOUBLE_A_STRING = InstructionSequenceReplacer.DOUBLE_A_STRING; private static final int STRING_A_STRING = InstructionSequenceReplacer.STRING_A_STRING; private static final int BOOLEAN_B_STRING = InstructionSequenceReplacer.BOOLEAN_B_STRING; private static final int CHAR_B_STRING = InstructionSequenceReplacer.CHAR_B_STRING; private static final int INT_B_STRING = InstructionSequenceReplacer.INT_B_STRING; private static final int LONG_B_STRING = InstructionSequenceReplacer.LONG_B_STRING; private static final int FLOAT_B_STRING = InstructionSequenceReplacer.FLOAT_B_STRING; private static final int DOUBLE_B_STRING = InstructionSequenceReplacer.DOUBLE_B_STRING; private static final int STRING_B_STRING = InstructionSequenceReplacer.STRING_B_STRING; /** * Creates a new instance of InstructionSequenceConstants, with constants * that reference classes from the given class pools. */ public InstructionSequenceConstants(ClassPool programClassPool, ClassPool libraryClassPool) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(programClassPool, libraryClassPool); // Create fieldref constants with wildcards, for fields in class X, // with name Y, and the given primitive types. ConstantPoolEditor constantPoolEditor = ____.getConstantPoolEditor(); final int FIELD_Z = constantPoolEditor.addConstant(new FieldrefConstant(X, constantPoolEditor.addConstant(new NameAndTypeConstant(Y, constantPoolEditor.addUtf8Constant("Z"))), null, null)); final int FIELD_B = constantPoolEditor.addConstant(new FieldrefConstant(X, constantPoolEditor.addConstant(new NameAndTypeConstant(Y, constantPoolEditor.addUtf8Constant("B"))), null, null)); final int FIELD_C = constantPoolEditor.addConstant(new FieldrefConstant(X, constantPoolEditor.addConstant(new NameAndTypeConstant(Y, constantPoolEditor.addUtf8Constant("C"))), null, null)); final int FIELD_S = constantPoolEditor.addConstant(new FieldrefConstant(X, constantPoolEditor.addConstant(new NameAndTypeConstant(Y, constantPoolEditor.addUtf8Constant("S"))), null, null)); final int FIELD_I = constantPoolEditor.addConstant(new FieldrefConstant(X, constantPoolEditor.addConstant(new NameAndTypeConstant(Y, constantPoolEditor.addUtf8Constant("I"))), null, null)); final int FIELD_F = constantPoolEditor.addConstant(new FieldrefConstant(X, constantPoolEditor.addConstant(new NameAndTypeConstant(Y, constantPoolEditor.addUtf8Constant("F"))), null, null)); final int FIELD_J = constantPoolEditor.addConstant(new FieldrefConstant(X, constantPoolEditor.addConstant(new NameAndTypeConstant(Y, constantPoolEditor.addUtf8Constant("J"))), null, null)); final int FIELD_D = constantPoolEditor.addConstant(new FieldrefConstant(X, constantPoolEditor.addConstant(new NameAndTypeConstant(Y, constantPoolEditor.addUtf8Constant("D"))), null, null)); // Create methodref constants with wildcards, for methods in class X, // with the given names and descriptors. final int EQUALS = constantPoolEditor.addConstant(new MethodrefConstant(X, constantPoolEditor.addNameAndTypeConstant(ClassConstants.METHOD_NAME_EQUALS, ClassConstants.METHOD_TYPE_EQUALS), null, null)); final int TO_STRING = constantPoolEditor.addConstant(new MethodrefConstant(X, constantPoolEditor.addNameAndTypeConstant(ClassConstants.METHOD_NAME_TOSTRING, ClassConstants.METHOD_TYPE_TOSTRING), null, null)); final int BOOLEAN_VALUE = constantPoolEditor.addConstant(new MethodrefConstant(X, constantPoolEditor.addNameAndTypeConstant("booleanValue", "()Z"), null, null)); final int BYTE_VALUE = constantPoolEditor.addConstant(new MethodrefConstant(X, constantPoolEditor.addNameAndTypeConstant("byteValue", "()B"), null, null)); final int CHAR_VALUE = constantPoolEditor.addConstant(new MethodrefConstant(X, constantPoolEditor.addNameAndTypeConstant("charValue", "()C"), null, null)); final int SHORT_VALUE = constantPoolEditor.addConstant(new MethodrefConstant(X, constantPoolEditor.addNameAndTypeConstant("shortValue", "()S"), null, null)); final int INT_VALUE = constantPoolEditor.addConstant(new MethodrefConstant(X, constantPoolEditor.addNameAndTypeConstant("intValue", "()I"), null, null)); final int FLOAT_VALUE = constantPoolEditor.addConstant(new MethodrefConstant(X, constantPoolEditor.addNameAndTypeConstant("floatValue", "()F"), null, null)); final int LONG_VALUE = constantPoolEditor.addConstant(new MethodrefConstant(X, constantPoolEditor.addNameAndTypeConstant("longValue", "()J"), null, null)); final int DOUBLE_VALUE = constantPoolEditor.addConstant(new MethodrefConstant(X, constantPoolEditor.addNameAndTypeConstant("doubleValue", "()D"), null, null)); final InstructionSequenceReplacer.Label TRY_START = InstructionSequenceReplacer.label(); final InstructionSequenceReplacer.Label TRY_END = InstructionSequenceReplacer.label(); final InstructionSequenceReplacer.Label CATCH_END = InstructionSequenceReplacer.label(); final InstructionSequenceReplacer.Label CATCH_EXCEPTION = InstructionSequenceReplacer.catch_(TRY_START.offset(), TRY_END.offset(), constantPoolEditor.addClassConstant(ClassConstants.NAME_JAVA_LANG_EXCEPTION, null)); VARIABLE_SEQUENCES = new Instruction[][][] { { // nop = nothing ____.nop().__(), }, { // iload/pop = nothing ____.iload(X) .pop().__(), }, { // lload/pop2 = nothing ____.lload(X) .pop2().__(), }, { // fload/pop = nothing ____.fload(X) .pop().__(), }, { // dload/pop2 = nothing ____.dload(X) .pop2().__(), }, { // aload/pop = nothing ____.aload(X) .pop().__(), }, { // i = i = nothing ____.iload(X) .istore(X).__(), }, { // l = l = nothing ____.lload(X) .lstore(X).__(), }, { // f = f = nothing ____.fload(X) .fstore(X).__(), }, { // d = d = nothing ____.dload(X) .dstore(X).__(), }, { // a = a = nothing ____.aload(X) .astore(X).__(), }, { // iload/iload = iload/dup ____.iload(X) .iload(X).__(), ____.iload(X) .dup().__() }, { // lload/lload = lload/dup2 ____.lload(X) .lload(X).__(), ____.lload(X) .dup2().__() }, { // fload/fload = fload/dup ____.fload(X) .fload(X).__(), ____.fload(X) .dup().__() }, { // dload/dload = dload/dup2 ____.dload(X) .dload(X).__(), ____.dload(X) .dup2().__() }, { // aload/aload = aload/dup ____.aload(X) .aload(X).__(), ____.aload(X) .dup().__() }, { // istore/istore = pop/istore ____.istore(X) .istore(X).__(), ____.pop() .istore(X).__() }, { // lstore/lstore = pop2/lstore ____.lstore(X) .lstore(X).__(), ____.pop2() .lstore(X).__() }, { // fstore/fstore = pop/fstore ____.fstore(X) .fstore(X).__(), ____.pop() .fstore(X).__() }, { // dstore/dstore = pop2/dstore ____.dstore(X) .dstore(X).__(), ____.pop2() .dstore(X).__() }, { // astore/astore = pop/astore ____.astore(X) .astore(X).__(), ____.pop() .astore(X).__() }, { // istore/iload = dup/istore ____.istore(X) .iload(X).__(), ____.dup() .istore(X).__() }, { // lstore/lload = dup2/lstore ____.lstore(X) .lload(X).__(), ____.dup2() .lstore(X).__() }, { // fstore/fload = dup/fstore ____.fstore(X) .fload(X).__(), ____.dup() .fstore(X).__() }, { // dstore/dload = dup2/dstore ____.dstore(X) .dload(X).__(), ____.dup2() .dstore(X).__() }, { // astore/aload = dup/astore ____.astore(X) .aload(X).__(), ____.dup() .astore(X).__() }, { // iload/dup/istore = iload ____.iload(X) .dup() .istore(X).__(), ____.iload(X).__() }, { // lload/dup2/lstore = lload ____.lload(X) .dup2() .lstore(X).__(), ____.lload(X).__() }, { // fload/dup/fstore = iload ____.fload(X) .dup() .fstore(X).__(), ____.fload(X).__() }, { // dload/dup2/dstore = dload ____.dload(X) .dup2() .dstore(X).__(), ____.dload(X).__() }, { // aload/dup/astore = aload ____.aload(X) .dup() .astore(X).__(), ____.aload(X).__() }, }; ARITHMETIC_SEQUENCES = new Instruction[][][] { { // c + i = i + c ____.iconst(A) .iload(X) .iadd().__(), ____.iload(X) .iconst(A) .iadd().__() }, { // b + i = i + b ____.bipush(A) .iload(X) .iadd().__(), ____.iload(X) .bipush(A) .iadd().__() }, { // s + i = i + s ____.sipush(A) .iload(X) .iadd().__(), ____.iload(X) .sipush(A) .iadd().__() }, { // c + i = i + c ____.ldc_(A) .iload(X) .iadd().__(), ____.iload(X) .ldc_(A) .iadd().__() }, { // c * i = i * c ____.sipush(A) .iload(X) .imul().__(), ____.iload(X) .sipush(A) .imul().__() }, { // b * i = i * b ____.bipush(A) .iload(X) .imul().__(), ____.iload(X) .bipush(A) .imul().__() }, { // s * i = i * s ____.sipush(A) .iload(X) .imul().__(), ____.iload(X) .sipush(A) .imul().__() }, { // c * i = i * c ____.ldc_(A) .iload(X) .imul().__(), ____.iload(X) .ldc_(A) .imul().__() }, { // c + l = l + c ____.lconst(A) .lload(X) .ladd().__(), ____.lload(X) .lconst(A) .ladd().__() }, { // c + l = l + c ____.ldc2_w(A) .lload(X) .ladd().__(), ____.lload(X) .ldc2_w(A) .ladd().__() }, { // c * l = l * c ____.lconst(A) .lload(X) .lmul().__(), ____.lload(X) .lconst(A) .lmul().__() }, { // c + f = f + c ____.fconst(A) .fload(X) .fadd().__(), ____.fload(X) .fconst(A) .fadd().__() }, { // c + f = f + c ____.ldc_(A) .fload(X) .fadd().__(), ____.fload(X) .ldc_(A) .fadd().__() }, { // c * f = f * c ____.fconst(A) .fload(X) .fmul().__(), ____.fload(X) .fconst(A) .fmul().__() }, { // c * f = f * c ____.ldc_(A) .fload(X) .lmul().__(), ____.fload(X) .ldc_(A) .lmul().__() }, { // c + d = d + c ____.dconst(A) .dload(X) .dadd().__(), ____.dload(X) .dconst(A) .dadd().__() }, { // c + d = d + c ____.ldc2_w(A) .dload(X) .dadd().__(), ____.dload(X) .ldc2_w(A) .dadd().__() }, { // c * d = d * c ____.dconst(A) .dload(X) .dmul().__(), ____.dload(X) .dconst(A) .dmul().__() }, { // c * d = d * c ____.ldc2_w(A) .dload(X) .dmul().__(), ____.dload(X) .ldc2_w(A) .dmul().__() }, { // i = i + c = i += c ____.iload(X) .sipush(A) .iadd() .istore(X).__(), ____.iinc(X, A).__() }, { // i = i + b = i += b ____.iload(X) .bipush(A) .iadd() .istore(X).__(), ____.iinc(X, A).__() }, { // i = i + s = i += s ____.iload(X) .sipush(A) .iadd() .istore(X).__(), ____.iinc(X, A).__() }, { // i = i - -1 = i++ ____.iload(X) .iconst_m1() .isub() .istore(X).__(), ____.iinc(X, 1).__() }, { // i = i - 1 = i-- ____.iload(X) .iconst_1() .isub() .istore(X).__(), ____.iinc(X, -1).__() }, { // i = i - 2 = i -= 2 ____.iload(X) .iconst_2() .isub() .istore(X).__(), ____.iinc(X, -2).__() }, { // i = i - 3 = i -= 3 ____.iload(X) .iconst_3() .isub() .istore(X).__(), ____.iinc(X, -3).__() }, { // i = i - 4 = i -= 4 ____.iload(X) .iconst_4() .isub() .istore(X).__(), ____.iinc(X, -4).__() }, { // i = i - 5 = i -= 5 ____.iload(X) .iconst_5() .isub() .istore(X).__(), ____.iinc(X, -5).__() }, { // ... + 0 = ... ____.iconst_0() .iadd().__(), }, { // ... + 0L = ... ____.lconst_0() .ladd().__(), }, // Not valid for -0.0. // { // ... + 0f = ... // ____.fconst_0() // .fadd().__(), // // }, // { // ... + 0d = ... // ____.dconst_0() // .dadd().__(), // // }, { // ... - 0 = ... ____.iconst_0() .isub().__(), }, { // ... - 0L = ... ____.lconst_0() .lsub().__(), }, { // ... - 0f = ... ____.fconst_0() .fsub().__(), }, { // ... - 0d = ... ____.dconst_0() .dsub().__(), }, { // ... * -1 = -... ____.iconst_m1() .imul().__(), ____.ineg().__() }, { // ... * 0 = 0 ____.iconst_0() .imul().__(), ____.pop() .iconst_0().__() }, { // ... * 1 = ... ____.iconst_1() .imul().__(), }, { // ... * 2 = ... << 1 ____.iconst_2() .imul().__(), ____.iconst_1() .ishl().__() }, { // ... * 4 = ... << 2 ____.iconst_4() .imul().__(), ____.iconst_2() .ishl().__() }, { // ... * 8 = ... << 3 ____.bipush(8) .imul().__(), ____.iconst_3() .ishl().__() }, { // ... * 16 = ... << 4 ____.bipush(16) .imul().__(), ____.bipush(4) .ishl().__() }, { // ... * 32 = ... << 5 ____.bipush(32) .imul().__(), ____.bipush(5) .ishl().__() }, { // ... * 64 = ... << 6 ____.bipush(64) .imul().__(), ____.bipush(6) .ishl().__() }, { // ... * 128 = ... << 7 ____.sipush(128) .imul().__(), ____.bipush(7) .ishl().__() }, { // ... * 256 = ... << 8 ____.sipush(256) .imul().__(), ____.bipush(8) .ishl().__() }, { // ... * 512 = ... << 9 ____.sipush(512) .imul().__(), ____.bipush(9) .ishl().__() }, { // ... * 1024 = ... << 10 ____.sipush(1024) .imul().__(), ____.bipush(10) .ishl().__() }, { // ... * 2048 = ... << 11 ____.sipush(2048) .imul().__(), ____.bipush(11) .ishl().__() }, { // ... * 4096 = ... << 12 ____.sipush(4096) .imul().__(), ____.bipush(12) .ishl().__() }, { // ... * 8192 = ... << 13 ____.sipush(8192) .imul().__(), ____.bipush(13) .ishl().__() }, { // ... * 16384 = ... << 14 ____.sipush(16384) .imul().__(), ____.bipush(14) .ishl().__() }, { // ... * 32768 = ... << 15 ____.ldc(32768) .imul().__(), ____.bipush(15) .ishl().__() }, { // ... * 65536 = ... << 16 ____.ldc(65536) .imul().__(), ____.bipush(16) .ishl().__() }, { // ... * 16777216 = ... << 24 ____.ldc(16777216) .imul().__(), ____.bipush(24) .ishl().__() }, { // ... * -1L = -... ____.ldc2_w(-1L) .lmul().__(), ____.lneg().__() }, { // ... * 0L = 0L ____.lconst_0() .lmul().__(), ____.pop2() .lconst_0().__() }, { // ... * 1L = ... ____.lconst_1() .lmul().__(), }, { // ... * 2L = ... << 1 ____.ldc2_w(2L) .lmul().__(), ____.iconst_1() .lshl().__() }, { // ... * 4L = ... << 2 ____.ldc2_w(4L) .lmul().__(), ____.iconst_2() .lshl().__() }, { // ... * 8L = ... << 3 ____.ldc2_w(8L) .lmul().__(), ____.iconst_3() .lshl().__() }, { // ... * 16L = ... << 4 ____.ldc2_w(16L) .lmul().__(), ____.bipush(4) .lshl().__() }, { // ... * 32L = ... << 5 ____.ldc2_w(32L) .lmul().__(), ____.bipush(5) .lshl().__() }, { // ... * 64L = ... << 6 ____.ldc2_w(64L) .lmul().__(), ____.bipush(6) .lshl().__() }, { // ... * 128L = ... << 7 ____.ldc2_w(128L) .lmul().__(), ____.bipush(7) .lshl().__() }, { // ... * 256L = ... << 8 ____.ldc2_w(256L) .lmul().__(), ____.bipush(8) .lshl().__() }, { // ... * 512L = ... << 9 ____.ldc2_w(512L) .lmul().__(), ____.bipush(9) .lshl().__() }, { // ... * 1024L = ... << 10 ____.ldc2_w(1024L) .lmul().__(), ____.bipush(10) .lshl().__() }, { // ... * 2048L = ... << 11 ____.ldc2_w(2048L) .lmul().__(), ____.bipush(11) .lshl().__() }, { // ... * 4096L = ... << 12 ____.ldc2_w(4096L) .lmul().__(), ____.bipush(12) .lshl().__() }, { // ... * 8192L = ... << 13 ____.ldc2_w(8192L) .lmul().__(), ____.bipush(13) .lshl().__() }, { // ... * 16384L = ... << 14 ____.ldc2_w(16384L) .lmul().__(), ____.bipush(14) .lshl().__() }, { // ... * 32768L = ... << 15 ____.ldc2_w(32768L) .lmul().__(), ____.bipush(15) .lshl().__() }, { // ... * 65536LL = ... << 16 ____.ldc2_w(65536L) .lmul().__(), ____.bipush(16) .lshl().__() }, { // ... * 16777216L = ... << 24 ____.ldc2_w(16777216L) .lmul().__(), ____.bipush(24) .lshl().__() }, { // ... * 4294967296L = ... << 32 ____.ldc2_w(4294967296L) .lmul().__(), ____.bipush(32) .lshl().__() }, { // ... * -1f = -... ____.ldc(-1f) .fmul().__(), ____.fneg().__() }, // Not valid for -0.0 and for NaN. // { // ... * 0f = 0f // ____.fconst_0() // .fmul().__(), // // ____.pop() // .fconst_0().__() // }, { // ... * 1f = ... ____.fconst_1() .fmul().__(), }, { // ... * -1d = -... ____.ldc2_w(-1.) .dmul().__(), ____.dneg().__() }, // Not valid for -0.0 and for NaN. // { // ... * 0d = 0d // ____.dconst_0() // .dmul().__(), // // ____.pop2() // .dconst_0().__() // }, { // ... * 1d = ... ____.dconst_1() .dmul().__(), }, { // ... / -1 = -... ____.iconst_m1() .idiv().__(), ____.ineg().__() }, { // ... / 1 = ... ____.iconst_1() .idiv().__(), }, // Not valid for negative values. // { // ... / 2 = ... >> 1 // ____.iconst_2() // .idiv().__(), // // ____.iconst_1() // .ishr().__() // }, // { // ... / 4 = ... >> 2 // ____.iconst_4() // .idiv().__(), // // ____.iconst_2() // .ishr().__() // }, // { // ... / 8 = ... >> 3 // ____.bipush(8) // .idiv().__(), // // ____.iconst_3() // .ishr().__() // }, // { // ... / 16 = ... >> 4 // ____.bipush(16) // .idiv().__(), // // ____.bipush(4) // .ishr().__() // }, // { // ... / 32 = ... >> 5 // ____.bipush(32) // .idiv().__(), // // ____.bipush(5) // .ishr().__() // }, // { // ... / 64 = ... >> 6 // ____.bipush(64) // .idiv().__(), // // ____.bipush(6) // .ishr().__() // }, // { // ... / 128 = ... >> 7 // ____.sipush(128) // .idiv().__(), // // ____.bipush(7) // .ishr().__() // }, // { // ... / 256 = ... >> 8 // ____.sipush(256) // .idiv().__(), // // ____.bipush(8) // .ishr().__() // }, // { // ... / 512 = ... >> 9 // ____.sipush(512) // .idiv().__(), // // ____.bipush(9) // .ishr().__() // }, // { // ... / 1024 = ... >> 10 // ____.sipush(1024) // .idiv().__(), // // ____.bipush(10) // .ishr().__() // }, // { // ... / 2048 = ... >> 11 // ____.sipush(2048) // .idiv().__(), // // ____.bipush(11) // .ishr().__() // }, // { // ... / 4096 = ... >> 12 // ____.sipush(4096) // .idiv().__(), // // ____.bipush(12) // .ishr().__() // }, // { // ... / 8192 = ... >> 13 // ____.sipush(8192) // .idiv().__(), // // ____.bipush(13) // .ishr().__() // }, // { // ... / 16384 = ... >> 14 // ____.sipush(16384) // .idiv().__(), // // ____.bipush(14) // .ishr().__() // }, // { // ... / 32768 = ... >> 15 // ____.ldc(32768) // .idiv().__(), // // ____.bipush(15) // .ishr().__() // }, // { // ... / 65536 = ... >> 16 // ____.ldc(65536) // .idiv().__(), // // ____.bipush(16) // .ishr().__() // }, // { // ... / 16777216 = ... >> 24 // ____.ldc(16777216) // .idiv().__(), // // ____.bipush(24) // .ishr().__() // }, { // ... / -1L = -... ____.ldc2_w(-1L) .ldiv().__(), ____.lneg().__() }, { // ... / 1L = ... ____.lconst_1() .ldiv().__(), }, // Not valid for negative values. // { // ... / 2L = ... >> 1 // ____.ldc2_w(2L) // .ldiv().__(), // // ____.iconst_1() // .lshr().__() // }, // { // ... / 4L = ... >> 2 // ____.ldc2_w(4L) // .ldiv().__(), // // ____.iconst_2() // .lshr().__() // }, // { // ... / 8L = ... >> 3 // ____.ldc2_w(8L) // .ldiv().__(), // // ____.iconst_3() // .lshr().__() // }, // { // ... / 16L = ... >> 4 // ____.ldc2_w(16L) // .ldiv().__(), // // ____.bipush(4) // .lshr().__() // }, // { // ... / 32L = ... >> 5 // ____.ldc2_w(32L) // .ldiv().__(), // // ____.bipush(5) // .lshr().__() // }, // { // ... / 64L = ... >> 6 // ____.ldc2_w(64L) // .ldiv().__(), // // ____.bipush(6) // .lshr().__() // }, // { // ... / 128L = ... >> 7 // ____.ldc2_w(128L) // .ldiv().__(), // // ____.bipush(7) // .lshr().__() // }, // { // ... / 256L = ... >> 8 // ____.ldc2_w(256L) // .ldiv().__(), // // ____.bipush(8) // .lshr().__() // }, // { // ... / 512L = ... >> 9 // ____.ldc2_w(512L) // .ldiv().__(), // // ____.bipush(9) // .lshr().__() // }, // { // ... / 1024L = ... >> 10 // ____.ldc2_w(1024L) // .ldiv().__(), // // ____.bipush(10) // .lshr().__() // }, // { // ... / 2048L = ... >> 11 // ____.ldc2_w(2048L) // .ldiv().__(), // // ____.bipush(11) // .lshr().__() // }, // { // ... / 4096L = ... >> 12 // ____.ldc2_w(4096L) // .ldiv().__(), // // ____.bipush(12) // .lshr().__() // }, // { // ... / 8192L = ... >> 13 // ____.ldc2_w(8192L) // .ldiv().__(), // // ____.bipush(13) // .lshr().__() // }, // { // ... / 16384L = ... >> 14 // ____.ldc2_w(16384L) // .ldiv().__(), // // ____.bipush(14) // .lshr().__() // }, // { // ... / 32768L = ... >> 15 // ____.ldc2_w(32768L) // .ldiv().__(), // // ____.bipush(15) // .lshr().__() // }, // { // ... / 65536LL = ... >> 16 // ____.ldc2_w(65536L) // .ldiv().__(), // // ____.bipush(16) // .lshr().__() // }, // { // ... / 16777216L = ... >> 24 // ____.ldc2_w(16777216L) // .ldiv().__(), // // ____.bipush(24) // .lshr().__() // }, // { // ... / 4294967296L = ... >> 32 // ____.ldc2_w(4294967296L) // .ldiv().__(), // // ____.bipush(32) // .lshr().__() // }, { // ... / -1f = -... ____.ldc(-1f) .fdiv().__(), ____.fneg().__() }, { // ... / 1f = ... ____.fconst_1() .fdiv().__(), }, { // ... / -1d = -... ____.ldc2_w(-1.) .ddiv().__(), ____.dneg().__() }, { // ... / 1d = ... ____.dconst_1() .ddiv().__(), }, { // ... % 1 = 0 ____.iconst_1() .irem().__(), ____.pop() .iconst_0().__() }, // Not valid for negative values. // { // ... % 2 = ... & 0x1 // ____.iconst_2() // .irem().__(), // // ____.iconst_1() // .iand().__() // }, // { // ... % 4 = ... & 0x3 // ____.iconst_4() // .irem().__(), // // ____.iconst_3() // .iand().__() // }, // { // ... % 8 = ... & 0x07 // ____.bipush(8) // .irem().__(), // // ____.bipush(0x07) // .iand().__() // }, // { // ... % 16 = ... & 0x0f // ____.bipush(16) // .irem().__(), // // ____.bipush(0x0f) // .iand().__() // }, // { // ... % 32 = ... & 0x1f // ____.bipush(32) // .irem().__(), // // ____.bipush(0x1f) // .iand().__() // }, // { // ... % 64 = ... & 0x3f // ____.bipush(64) // .irem().__(), // // ____.bipush(0x3f) // .iand().__() // }, // { // ... % 128 = ... & 0x7f // ____.sipush(128) // .irem().__(), // // ____.bipush(0x7f) // .iand().__() // }, // { // ... % 256 = ... & 0x00ff // ____.sipush(256) // .irem().__(), // // ____.sipush(0x00ff) // .iand().__() // }, // { // ... % 512 = ... & 0x01ff // ____.sipush(512) // .irem().__(), // // ____.sipush(0x01ff) // .iand().__() // }, // { // ... % 1024 = ... & 0x03ff // ____.sipush(1024) // .irem().__(), // // ____.sipush(0x03ff) // .iand().__() // }, // { // ... % 2048 = ... & 0x07ff // ____.sipush(2048) // .irem().__(), // // ____.sipush(0x07ff) // .iand().__() // }, // { // ... % 4096 = ... & 0x0fff // ____.sipush(4096) // .irem().__(), // // ____.sipush(0x0fff) // .iand().__() // }, // { // ... % 8192 = ... & 0x1fff // ____.sipush(8192) // .irem().__(), // // ____.sipush(0x1fff) // .iand().__() // }, // { // ... % 16384 = ... & 0x3fff // ____.sipush(16384) // .irem().__(), // // ____.sipush(0x3fff) // .iand().__() // }, { // ... % 1L = 0L ____.lconst_1() .lrem().__(), ____.pop2() .lconst_0().__() }, // { // ... % 1f = 0f // ____.fconst_1() // .frem().__(), // // ____.pop() // .fconst_0().__() // }, // { // ... % 1d = 0d // ____.dconst_1() // .drem().__(), // // ____.pop2() // .dconst_0().__() // }, { // -(-...) = ... ____.ineg() .ineg().__(), }, { // -(-...) = ... ____.lneg() .lneg().__(), }, { // -(-...) = ... ____.fneg() .fneg().__(), }, { // -(-...) = ... ____.dneg() .dneg().__(), }, { // +(-...) = -... ____.ineg() .iadd().__(), ____.isub().__() }, { // +(-...) = -... ____.lneg() .ladd().__(), ____.lsub().__() }, { // +(-...) = -... ____.fneg() .fadd().__(), ____.fsub().__() }, { // +(-...) = -... ____.dneg() .dadd().__(), ____.dsub().__() }, { // ... << 0 = ... ____.iconst_0() .ishl().__(), }, { // ... << 0 = ... ____.iconst_0() .lshl().__(), }, { // ... >> 0 = ... ____.iconst_0() .ishr().__(), }, { // ... >> 0 = ... ____.iconst_0() .lshr().__(), }, { // ... >>> 0 = ... ____.iconst_0() .iushr().__(), }, { // ... >>> 0 = ... ____.iconst_0() .lushr().__(), }, { // ... & -1 = ... ____.iconst_m1() .iand().__(), }, { // ... & 0 = 0 ____.iconst_0() .iand().__(), ____.pop() .iconst_0().__() }, { // ... & -1L = ... ____.ldc2_w(-1L) .land().__(), }, { // ... & 0L = 0L ____.lconst_0() .land().__(), ____.pop2() .lconst_0().__() }, { // ... | -1 = -1 ____.iconst_m1() .ior().__(), ____.pop() .iconst_m1().__() }, { // ... | 0 = ... ____.iconst_0() .ior().__(), }, { // ... | -1L = -1L ____.ldc2_w(-1L) .land().__(), ____.pop2() .ldc2_w(-1L).__() }, { // ... | 0L = ... ____.lconst_0() .lor().__(), }, { // ... ^ 0 = ... ____.iconst_0() .ixor().__(), }, { // ... ^ 0L = ... ____.lconst_0() .lxor().__(), }, { // (... & 0x0000ff00) >> 8 = (... >> 8) & 0xff ____.ldc(0x0000ff00) .iand() .bipush(8) .ishr().__(), ____.bipush(8) .ishr() .sipush(0xff) .iand().__() }, { // (... & 0x0000ff00) >>> 8 = (... >>> 8) & 0xff ____.ldc(0x0000ff00) .iand() .bipush(8) .iushr().__(), ____.bipush(8) .iushr() .sipush(0xff) .iand().__() }, { // (... & 0x00ff0000) >> 16 = (... >> 16) & 0xff ____.ldc(0x00ff0000) .iand() .bipush(16) .ishr().__(), ____.bipush(16) .ishr() .sipush(0xff) .iand().__() }, { // (... & 0x00ff0000) >>> 16 = (... >>> 16) & 0xff ____.ldc(0x00ff0000) .iand() .bipush(16) .iushr().__(), ____.bipush(16) .iushr() .sipush(0xff) .iand().__() }, { // (... & 0xff000000) >> 24 = ... >> 24 ____.ldc(0xff000000) .iand() .bipush(24) .ishr().__(), ____.bipush(24) .ishr().__() }, { // (... & 0xffff0000) >> 16 = ... >> 16 ____.ldc(0xffff0000) .iand() .bipush(16) .ishr().__(), ____.bipush(16) .ishr().__() }, { // (... & 0xffff0000) >>> 16 = ... >>> 16 ____.ldc(0xffff0000) .iand() .bipush(16) .iushr().__(), ____.bipush(16) .iushr().__() }, { // (... >> 24) & 0xff = ... >>> 24 ____.bipush(24) .ishr() .sipush(0xff) .iand().__(), ____.bipush(24) .iushr().__() }, { // (... >>> 24) & 0xff = ... >>> 24 ____.bipush(24) .iushr() .sipush(0xff) .iand().__(), ____.bipush(24) .iushr().__() }, { // (byte)(... & 0x000000ff) = (byte)... ____.sipush(0xff) .iand() .i2b().__(), ____.i2b().__() }, { // (char)(... & 0x0000ffff) = (char)... ____.ldc(0x0000ffff) .iand() .i2c().__(), ____.i2c().__() }, { // (short)(... & 0x0000ffff) = (short)... ____.ldc(0x0000ffff) .iand() .i2s().__(), ____.i2s().__() }, // The Dalvik VM on Android 4.4 throws a VFY error or crashes if // the byte/short cast is removed before an array store. // { // (byte)(... >> 24) = ... >> 24 // ____.bipush(24) // .ishr() // .i2b().__(), // // ____.bipush(24) // .ishr().__() // }, // { // (byte)(... >>> 24) = ... >> 24 // ____.bipush(24) // .iushr() // .i2b().__(), // // ____.bipush(24) // .ishr().__() // }, // { // (char)(... >> 16) = ... >>> 16 // ____.bipush(16) // .ishr() // .i2c().__(), // // ____.bipush(16) // .iushr().__() // }, // { // (char)(... >>> 16) = ... >>> 16 // ____.bipush(16) // .iushr() // .i2c().__(), // // ____.bipush(16) // .iushr().__() // }, // { // (short)(... >> 16) = ... >> 16 // ____.bipush(16) // .ishr() // .i2s().__(), // // ____.bipush(16) // .ishr().__() // }, // { // (short)(... >>> 16) = ... >> 16 // ____.bipush(16) // .iushr() // .i2s().__(), // // ____.bipush(16) // .ishr().__() // }, { // ... << 24 >> 24 = (byte)... ____.bipush(24) .ishl() .bipush(24) .ishr().__(), ____.i2b().__() }, { // ... << 16 >>> 16 = (char)... ____.bipush(16) .ishl() .bipush(16) .iushr().__(), ____.i2c().__() }, { // ... << 16 >> 16 = (short)... ____.bipush(16) .ishl() .bipush(16) .ishr().__(), ____.i2s().__() }, { // ... << 32 >> 32 = (long)(int)... ____.bipush(32) .lshl() .bipush(32) .lshr().__(), ____.l2i() .i2l().__() }, { // (int)(... & 0x00000000ffffffffL) = (int)... ____.ldc2_w(0x00000000ffffffffL) .land() .l2i().__(), ____.l2i().__() }, { // (... & 0xffffffff00000000L) >> 32 = ... >> 32 ____.ldc2_w(0xffffffff00000000L) .land() .bipush(32) .lshr().__(), ____.bipush(32) .lshr().__() }, { // (... & 0xffffffff00000000L) >>> 32 = ... >>> 32 ____.ldc2_w(0xffffffff00000000L) .land() .bipush(32) .lushr().__(), ____.bipush(32) .lushr().__() }, { // ... += 0 = nothing ____.iinc(X, 0).__(), }, }; FIELD_SEQUENCES = new Instruction[][][] { { // getfield/putfield = nothing ____.aload(X) .aload(X) .getfield(Y) .putfield(Y).__(), }, // { // putfield_L/putfield_L = pop2_x1/putfield // ____.aload(X) // // ... // .putfield(FIELD_J) // .aload(X) // // ... // .putfield(FIELD_J).__(), // // ____.aload(X) // // ... // .pop2() // // ... // .putfield(FIELD_J).__() // }, // { // putfield_D/putfield_D = pop2_x1/putfield // ____.aload(X) // // ... // .putfield(FIELD_D) // .aload(X) // // ... // .putfield(FIELD_D).__(), // // ____.aload(X) // // ... // .pop2() // // ... // .putfield(FIELD_D).__() // }, // { // putfield/putfield = pop_x1/putfield // ____.aload(X) // // ... // .putfield(Y) // .aload(X) // // ... // .putfield(Y).__(), // // ____.aload(X) // // ... // .pop() // // ... // .putfield(Y).__() // }, // { // putfield_L/getfield_L = dup2_x1/putfield // ____.aload(X) // // ... // .putfield(FIELD_J) // .aload(X) // .getfield(FIELD_J).__(), // // ____.aload(X) // // ... // .dup2_x1() // .putfield(FIELD_J).__() // }, // { // putfield_D/getfield_D = dup2_x1/putfield // ____.aload(X) // // ... // .putfield(FIELD_D) // .aload(X) // .getfield(FIELD_D).__(), // // ____.aload(X) // // ... // .dup2_x1() // .putfield(FIELD_D).__() // }, // { // putfield/getfield = dup_x1/putfield // ____.aload(X) // // ... // .putfield(Y) // .aload(X) // .getfield(Y).__(), // // ____.aload(X) // // ... // .dup_x1() // .putfield(Y).__() // }, { // getstatic/putstatic = nothing ____.getstatic(X) .putstatic(X).__(), }, { // getstatic_L/getstatic_L = getstatic/dup2 ____.getstatic(FIELD_J) .getstatic(FIELD_J).__(), ____.getstatic(FIELD_J) .dup2().__() }, { // getstatic_D/getstatic_D = getstatic/dup2 ____.getstatic(FIELD_D) .getstatic(FIELD_D).__(), ____.getstatic(FIELD_D) .dup2().__() }, { // getstatic/getstatic = getstatic/dup ____.getstatic(X) .getstatic(X).__(), ____.getstatic(X) .dup().__() }, { // putstatic_L/putstatic_L = pop2/putstatic ____.putstatic(FIELD_J) .putstatic(FIELD_J).__(), ____.pop2() .putstatic(FIELD_J).__() }, { // putstatic_D/putstatic_D = pop2/putstatic ____.putstatic(FIELD_D) .putstatic(FIELD_D).__(), ____.pop2() .putstatic(FIELD_D).__() }, { // putstatic/putstatic = pop/putstatic ____.putstatic(X) .putstatic(X).__(), ____.pop() .putstatic(X).__() }, { // putstatic_L/getstatic_L = dup2/putstatic ____.putstatic(FIELD_J) .getstatic(FIELD_J).__(), ____.dup2() .putstatic(FIELD_J).__() }, { // putstatic_D/getstatic_D = dup2/putstatic ____.putstatic(FIELD_D) .getstatic(FIELD_D).__(), ____.dup2() .putstatic(FIELD_D).__() }, { // putstatic/getstatic = dup/putstatic ____.putstatic(X) .getstatic(X).__(), ____.dup() .putstatic(X).__() }, { // L i L: getfield_L/iload/getfield_L = iload/getfield_L/dup2_x1 ____.aload(A) .getfield(FIELD_J) .iload(B) .aload(A) .getfield(FIELD_J).__(), ____.iload(B) .aload(A) .getfield(FIELD_J) .dup2_x1().__() }, { // D i D: getfield_D/iload/getfield_D = iload/getfield_D/dup2_x1 ____.aload(A) .getfield(FIELD_D) .iload(B) .aload(A) .getfield(FIELD_D).__(), ____.iload(B) .aload(A) .getfield(FIELD_D) .dup2_x1().__() }, { // X i X (e.g. X[i] = X[.] ...): getfield/iload/getfield = iload/getfield/dup_x1 ____.aload(A) .getfield(X) .iload(B) .aload(A) .getfield(X).__(), ____.iload(B) .aload(A) .getfield(X) .dup_x1().__() }, { // L i L: getstatic_L/iload/getstatic_L = iload/getstatic_L/dup2_x1 ____.getstatic(FIELD_J) .iload(A) .getstatic(FIELD_J).__(), ____.iload(A) .getstatic(FIELD_J) .dup2_x1().__() }, { // D i D: getstatic_D/iload/getstatic_D = iload/getstatic_D/dup2_x1 ____.getstatic(FIELD_D) .iload(A) .getstatic(FIELD_D).__(), ____.iload(A) .getstatic(FIELD_D) .dup2_x1().__() }, { // X i X (e.g. X[i] = X[.] ...): getstatic/iload/getstatic = iload/getstatic/dup_x1 ____.getstatic(X) .iload(A) .getstatic(X).__(), ____.iload(A) .getstatic(X) .dup_x1().__() }, { // X[i] j X[i] (e.g. X[i][j] = X[i][.] ...): getfield/iload/aaload/iload/getfield/iload/aaload = iload/getfield//iload/aaload/iload/dup_x1 ____.aload(A) .getfield(X) .iload(B) .aaload() .iload(C) .aload(A) .getfield(X) .iload(B) .aaload().__(), ____.iload(C) .aload(A) .getfield(X) .iload(B) .aaload() .dup_x1().__() }, { // X[i] j X[i] (e.g. X[i][j] = X[i][.] ...): getstatic/iload/aaload/iload/getstatic/iload/aaload = iload/getstatic//iload/aaload/iload/dup_x1 ____.getstatic(X) .iload(B) .aaload() .iload(C) .getstatic(X) .iload(B) .aaload().__(), ____.iload(C) .getstatic(X) .iload(B) .aaload() .dup_x1().__() }, }; CAST_SEQUENCES = new Instruction[][][] { { // (byte)(byte)... = (byte)... ____.i2b() .i2b().__(), ____.i2b().__() }, { // (byte)(char)... = (byte)... ____.i2c() .i2b().__(), ____.i2b().__() }, { // (byte)(short)... = (byte)... ____.i2s() .i2b().__(), ____.i2b().__() }, { // (char)(char)... = (char)... ____.i2c() .i2c().__(), ____.i2c().__() }, { // (char)(short)... = (char)... ____.i2s() .i2c().__(), ____.i2c().__() }, // { // (short)(byte)... = (byte)... // ____.i2b() // .i2s().__(), // // ____.i2b().__() // }, { // (short)(char)... = (short)... ____.i2c() .i2s().__(), ____.i2s().__() }, { // (short)(short)... = (short)... ____.i2s() .i2s().__(), ____.i2s().__() }, { // (int)(long)... = ... ____.i2l() .l2i().__(), }, { // (int)(double)... = ... ____.i2d() .d2i().__(), }, { // (float)(double)... = (float)... for ints ____.i2d() .d2f().__(), ____.i2f().__() }, { // (float)(double)... = (float)... for longs ____.l2d() .d2f().__(), ____.l2f().__() }, { // (int)(double)... = (int)... ____.f2d() .d2i().__(), ____.f2i().__() }, { // (long)(double)... = (long)... ____.f2d() .d2l().__(), ____.f2l().__() }, { // (X)(X)... = (X)... ____.checkcast(X) .checkcast(X).__(), ____.checkcast(X).__() }, // Not handled correctly in all cases by VMs prior to Java 6... // { // (byte)bytes[...] = bytes[...] // ____.baload() // .i2b().__(), // // ____.baload().__() // }, // { // (short)bytes[...] = bytes[...] // ____.baload() // .i2s().__(), // // ____.baload().__() // }, // { // (char)chars[...] = chars[...] // ____.caload() // .i2c().__(), // // ____.caload().__() // }, // { // (short)shorts[...] = shorts[...] // ____.saload() // .i2s().__(), // // ____.saload().__() // }, // { // bytes[...] = (byte)... = bytes[...] = ... // ____.i2b() // .bastore().__(), // // ____.bastore().__() // }, // { // chars[...] = (char)... = chars[...] = ... // ____.i2c() // .castore().__(), // // ____.castore().__() // }, // { // shorts[...] = (short)... = shorts[...] = ... // ____.i2s() // .sastore().__(), // // ____.sastore().__() // }, }; BRANCH_SEQUENCES = new Instruction[][][] { { // goto +3 = nothing ____.goto_(3).__(), }, { // ifeq +3 = pop ____.ifeq(3).__(), ____.pop().__() }, { // ifne +3 = pop ____.ifne(3).__(), ____.pop().__() }, { // iflt +3 = pop ____.iflt(3).__(), ____.pop().__() }, { // ifge +3 = pop ____.ifge(3).__(), ____.pop().__() }, { // ifgt +3 = pop ____.ifgt(3).__(), ____.pop().__() }, { // ifle +3 = pop ____.ifle(3).__(), ____.pop().__() }, { // ificmpeq +3 = pop2 ____.ificmpeq(3).__(), ____.pop2().__() }, { // ificmpne +3 = pop2 ____.ificmpne(3).__(), ____.pop2().__() }, { // ificmplt +3 = pop2 ____.ificmplt(3).__(), ____.pop2().__() }, { // ificmpge +3 = pop2 ____.ificmpge(3).__(), ____.pop2().__() }, { // ificmpgt +3 = pop2 ____.ificmpgt(3).__(), ____.pop2().__() }, { // ificmple +3 = pop2 ____.ificmple(3).__(), ____.pop2().__() }, { // ifacmpeq +3 = pop2 ____.ifacmpeq(3).__(), ____.pop2().__() }, { // ifacmpne +3 = pop2 ____.ifacmpne(3).__(), ____.pop2().__() }, { // ifnull +3 = pop ____.ifnull(3).__(), ____.pop().__() }, { // ifnonnull +3 = pop ____.ifnonnull(3).__(), ____.pop().__() }, { // if (... == 0) = ifeq ____.iconst_0() .ificmpeq(X).__(), ____.ifeq(X).__() }, { // if (0 == i) = iload/ifeq ____.iconst_0() .iload(Y) .ificmpeq(X).__(), ____.iload(Y) .ifeq(X).__() }, { // if (0 == i) = getstatic/ifeq ____.iconst_0() .getstatic(Y) .ificmpeq(X).__(), ____.getstatic(Y) .ifeq(X).__() }, { // if (0 == i) = getfield/ifeq ____.iconst_0() .aload(Y) .getfield(Z) .ificmpeq(X).__(), ____.aload(Y) .getfield(Z) .ifeq(X).__() }, { // if (... != 0) = ifne ____.iconst_0() .ificmpne(X).__(), ____.ifne(X).__() }, { // if (0 != i) = iload/ifeq ____.iconst_0() .iload(Y) .ificmpne(X).__(), ____.iload(Y) .ifne(X).__() }, { // if (0 != i) = getstatic/ifeq ____.iconst_0() .getstatic(Y) .ificmpne(X).__(), ____.getstatic(Y) .ifne(X).__() }, { // if (0 != i) = getfield/ifeq ____.iconst_0() .aload(Y) .getfield(Z) .ificmpne(X).__(), ____.aload(Y) .getfield(Z) .ifne(X).__() }, { // if (... < 0) = iflt ____.iconst_0() .ificmplt(X).__(), ____.iflt(X).__() }, { // if (... < 1) = ifle ____.iconst_1() .ificmplt(X).__(), ____.ifle(X).__() }, { // if (0 > i) = iload/iflt ____.iconst_0() .iload(Y) .ificmpgt(X).__(), ____.iload(Y) .iflt(X).__() }, { // if (1 > i) = iload/ifle ____.iconst_1() .iload(Y) .ificmpgt(X).__(), ____.iload(Y) .ifle(X).__() }, { // if (0 > i) = getstatic/iflt ____.iconst_0() .getstatic(Y) .ificmpgt(X).__(), ____.getstatic(Y) .iflt(X).__() }, { // if (1 > i) = getstatic/ifle ____.iconst_1() .getstatic(Y) .ificmpgt(X).__(), ____.getstatic(Y) .ifle(X).__() }, { // if (0 > i) = getfield/iflt ____.iconst_0() .aload(Y) .getfield(Z) .ificmpgt(X).__(), ____.aload(Y) .getfield(Z) .iflt(X).__() }, { // if (1 > i) = getfield/ifle ____.iconst_1() .aload(Y) .getfield(Z) .ificmpgt(X).__(), ____.aload(Y) .getfield(Z) .ifle(X).__() }, { // if (... >= 0) = ifge ____.iconst_0() .ificmpge(X).__(), ____.ifge(X).__() }, { // if (... >= 1) = ifgt ____.iconst_1() .ificmpge(X).__(), ____.ifgt(X).__() }, { // if (0 <= i) = iload/ifge ____.iconst_0() .iload(Y) .ificmple(X).__(), ____.iload(Y) .ifge(X).__() }, { // if (1 <= i) = iload/ifgt ____.iconst_1() .iload(Y) .ificmple(X).__(), ____.iload(Y) .ifgt(X).__() }, { // if (0 <= i) = getstatic/ifge ____.iconst_0() .getstatic(Y) .ificmple(X).__(), ____.getstatic(Y) .ifge(X).__() }, { // if (1 <= i) = getstatic/ifgt ____.iconst_1() .getstatic(Y) .ificmple(X).__(), ____.getstatic(Y) .ifgt(X).__() }, { // if (0 <= i) = getfield/ifge ____.iconst_0() .aload(Y) .getfield(Z) .ificmple(X).__(), ____.aload(Y) .getfield(Z) .ifge(X).__() }, { // if (1 <= i) = getfield/ifgt ____.iconst_1() .aload(Y) .getfield(Z) .ificmple(X).__(), ____.aload(Y) .getfield(Z) .ifgt(X).__() }, { // if (... > 0) = ifgt ____.iconst_0() .ificmpgt(X).__(), ____.ifgt(X).__() }, { // if (... > -1) = ifge ____.iconst_m1() .ificmpgt(X).__(), ____.ifge(X).__() }, { // if (0 < i) = iload/ifgt ____.iconst_0() .iload(Y) .ificmplt(X).__(), ____.iload(Y) .ifgt(X).__() }, { // if (-1 < i) = iload/ifge ____.iconst_m1() .iload(Y) .ificmplt(X).__(), ____.iload(Y) .ifge(X).__() }, { // if (0 < i) = getstatic/ifgt ____.iconst_0() .getstatic(Y) .ificmplt(X).__(), ____.getstatic(Y) .ifgt(X).__() }, { // if (-1 < i) = getstatic/ifge ____.iconst_m1() .getstatic(Y) .ificmplt(X).__(), ____.getstatic(Y) .ifge(X).__() }, { // if (0 < i) = getfield/ifgt ____.iconst_0() .aload(Y) .getfield(Z) .ificmplt(X).__(), ____.aload(Y) .getfield(Z) .ifgt(X).__() }, { // if (-1 < i) = getfield/ifge ____.iconst_m1() .aload(Y) .getfield(Z) .ificmplt(X).__(), ____.aload(Y) .getfield(Z) .ifge(X).__() }, { // if (... <= 0) = ifle ____.iconst_0() .ificmple(X).__(), ____.ifle(X).__() }, { // if (... <= -1) = iflt ____.iconst_m1() .ificmple(X).__(), ____.iflt(X).__() }, { // if (0 >= i) = iload/ifle ____.iconst_0() .iload(Y) .ificmpge(X).__(), ____.iload(Y) .ifle(X).__() }, { // if (-1 >= i) = iload/iflt ____.iconst_m1() .iload(Y) .ificmpge(X).__(), ____.iload(Y) .iflt(X).__() }, { // if (0 >= i) = getstatic/ifle ____.iconst_0() .getstatic(Y) .ificmpge(X).__(), ____.getstatic(Y) .ifle(X).__() }, { // if (-1 >= i) = getstatic/iflt ____.iconst_m1() .getstatic(Y) .ificmpge(X).__(), ____.getstatic(Y) .iflt(X).__() }, { // if (0 >= i) = getfield/ifle ____.iconst_0() .aload(Y) .getfield(Z) .ificmpge(X).__(), ____.aload(Y) .getfield(Z) .ifle(X).__() }, { // if (-1 >= i) = getfield/iflt ____.iconst_m1() .aload(Y) .getfield(Z) .ificmpge(X).__(), ____.aload(Y) .getfield(Z) .iflt(X).__() }, { // if (... == null) = ifnull ____.aconst_null() .ifacmpeq(X).__(), ____.ifnull(X).__() }, { // if (null == a) = aload/ifnull ____.aconst_null() .aload(Y) .ifacmpeq(X).__(), ____.aload(Y) .ifnull(X).__() }, { // if (null == a) = getstatic/ifnull ____.aconst_null() .getstatic(Y) .ifacmpeq(X).__(), ____.getstatic(Y) .ifnull(X).__() }, { // if (null == a) = getfield/ifnull ____.aconst_null() .aload(Y) .getfield(Z) .ifacmpeq(X).__(), ____.aload(Y) .getfield(Z) .ifnull(X).__() }, { // if (... != null) = ifnonnull ____.aconst_null() .ifacmpne(X).__(), ____.ifnonnull(X).__() }, { // if (null != a) = aload/ifnonnull ____.aconst_null() .aload(Y) .ifacmpne(X).__(), ____.aload(Y) .ifnonnull(X).__() }, { // if (null != a) = getstatic/ifnonnull ____.aconst_null() .getstatic(Y) .ifacmpne(X).__(), ____.getstatic(Y) .ifnonnull(X).__() }, { // if (null != a) = getfield/ifnonnull ____.aconst_null() .aload(Y) .getfield(Z) .ifacmpne(X).__(), ____.aload(Y) .getfield(Z) .ifnonnull(X).__() }, { // iconst_0/ifeq = goto ____.iconst_0() .ifeq(X).__(), ____.goto_(X).__() }, { // iconst/ifeq = nothing ____.iconst(A) .ifeq(X).__(), }, { // bipush/ifeq = nothing ____.bipush(A) .ifeq(X).__(), }, { // sipush/ifeq = nothing ____.sipush(A) .ifeq(X).__(), }, { // iconst_0/ifne = nothing ____.iconst_0() .ifne(X).__(), }, { // iconst/ifne = goto ____.iconst(A) .ifne(X).__(), ____.goto_(X).__() }, { // bipush/ifne = goto ____.bipush(A) .ifne(X).__(), ____.goto_(X).__() }, { // sipush/ifne = goto ____.sipush(A) .ifne(X).__(), ____.goto_(X).__() }, { // iconst_0/iflt = nothing ____.iconst_0() .iflt(X).__(), }, { // iconst_0/ifge = goto ____.iconst_0() .ifge(X).__(), ____.goto_(X).__() }, { // iconst_0/ifgt = nothing ____.iconst_0() .ifgt(X).__(), }, { // iconst_0/ifle = goto ____.iconst_0() .ifle(X).__(), ____.goto_(X).__() }, { // aconst_null/ifnull = goto ____.aconst_null() .ifnull(X).__(), ____.goto_(X).__() }, { // aconst_null/ifnonnul = nothing ____.aconst_null() .ifnonnull(X).__(), }, { // ifeq/goto = ifne ____.ifeq(6) .goto_(X).__(), ____.ifne(X).__() }, { // ifne/goto = ifeq ____.ifne(6) .goto_(X).__(), ____.ifeq(X).__() }, { // iflt/goto = ifge ____.iflt(6) .goto_(X).__(), ____.ifge(X).__() }, { // ifge/goto = iflt ____.ifge(6) .goto_(X).__(), ____.iflt(X).__() }, { // ifgt/goto = ifle ____.ifgt(6) .goto_(X).__(), ____.ifle(X).__() }, { // ifle/goto = ifgt ____.ifle(6) .goto_(X).__(), ____.ifgt(X).__() }, { // ificmpeq/goto = ificmpne ____.ificmpeq(6) .goto_(X).__(), ____.ificmpne(X).__() }, { // ificmpne/goto = ificmpeq ____.ificmpne(6) .goto_(X).__(), ____.ificmpeq(X).__() }, { // ificmplt/goto = ificmpge ____.ificmplt(6) .goto_(X).__(), ____.ificmpge(X).__() }, { // ificmpge/goto = ificmplt ____.ificmpge(6) .goto_(X).__(), ____.ificmplt(X).__() }, { // ificmpgt/goto = ificmple ____.ificmpgt(6) .goto_(X).__(), ____.ificmple(X).__() }, { // ificmple/goto = ificmpgt ____.ificmple(6) .goto_(X).__(), ____.ificmpgt(X).__() }, { // ifacmpeq/goto = ifacmpne ____.ifacmpeq(6) .goto_(X).__(), ____.ifacmpne(X).__() }, { // ifacmpne/goto = ifacmpeq ____.ifacmpne(6) .goto_(X).__(), ____.ifacmpeq(X).__() }, { // ifnull/goto = ifnonnull ____.ifnull(6) .goto_(X).__(), ____.ifnonnull(X).__() }, { // ifnonnull/goto = ifnull ____.ifnonnull(6) .goto_(X).__(), ____.ifnull(X).__() }, // { // switch (...) { default: ... } = pop/goto ... // ____.tableswitch(A, X, Y, 0, new int[0]).__(), // // ____.pop() // .goto_(A).__() // }, // { // switch (...) { default: ... } = pop/goto ... // ____.lookupswitch(A, 0, new int[0], new int[0]).__(), // // ____.pop() // .goto_(A).__() // }, { // switch (...) { case/case/default: ... } = switch (...) { case/default: ... } ____.lookupswitch(A, new int[] { X, Y }, new int[] { A, B }).__(), ____.lookupswitch(A, new int[] { Y }, new int[] { B }).__() }, { // switch (...) { case/case/default: ... } = switch (...) { case/default: ... } ____.lookupswitch(B, new int[] { X, Y }, new int[] { A, B }).__(), ____.lookupswitch(B, new int[] { X }, new int[] { A }).__() }, { // switch (...) { case/case/case/default: ... } = switch (...) { case/case/default: ... } ____.lookupswitch(A, new int[] { X, Y, Z }, new int[] { A, B, C }).__(), ____.lookupswitch(A, new int[] { Y, Z }, new int[] { B, C }).__() }, { // switch (...) { case/case/case/default: ... } = switch (...) { case/case/default: ... } ____.lookupswitch(B, new int[] { X, Y, Z }, new int[] { A, B, C }).__(), ____.lookupswitch(B, new int[] { X, Z }, new int[] { A, C }).__() }, { // switch (...) { case/case/case/default: ... } = switch (...) { case/case/default: ... } ____.lookupswitch(C, new int[] { X, Y, Z }, new int[] { A, B, C }).__(), ____.lookupswitch(C, new int[] { X, Y }, new int[] { A, B }).__() }, // { // switch (...) { case ...: ... default: ... } // // = if (... == ...) ... else ... // ____.tableswitch(A, X, Y, 1, new int[] { B }).__(), // // ____.sipush(X) // .ificmpne(A) // .goto_(B).__() // }, // { // switch (...) { case ...: ... default: ... } // // = if (... == ...) ... else ... // ____.lookupswitch(A, 1, new int[] { X }, new int[] { B }).__(), // // ____.sipush(X) // .ificmpne(A) // .goto_(B).__() // } }; OBJECT_SEQUENCES = new Instruction[][][] { { // "...".equals("...") = X.class.equals(X.class) = true (ignoring class loader) ____.ldc_(A) .ldc_(A) .invokevirtual(EQUALS).__(), ____.iconst_1().__() }, { // ....equals(dup) = true (discarding any NullPointerException) ____.dup() .invokevirtual(EQUALS).__(), ____.pop() .iconst_1().__() }, { // object.equals(object) = true (ignoring implementation and discarding any NullPointerException) ____.aload(A) .aload(A) .invokevirtual(EQUALS).__(), ____.iconst_1().__() }, { // object.equals(object) = true (ignoring implementation and discarding any NullPointerException) ____.getstatic(A) .getstatic(A) .invokevirtual(EQUALS).__(), ____.iconst_1().__() }, { // object.equals(object) = true (ignoring implementation and discarding any NullPointerException) ____.aload(A) .getfield(B) .aload(A) .getfield(B) .invokevirtual(EQUALS).__(), ____.iconst_1().__() }, { // Boolean.valueOf(false) = Boolean.FALSE ____.iconst_0() .invokestatic(BOOLEAN, "valueOf", "(Z)Ljava/lang/Boolean;").__(), ____.getstatic(BOOLEAN, "FALSE", "Ljava/lang/Boolean;").__() }, { // Boolean.valueOf(true) = Boolean.TRUE ____.iconst_1() .invokestatic(BOOLEAN, "valueOf", "(Z)Ljava/lang/Boolean;").__(), ____.getstatic(BOOLEAN, "TRUE", "Ljava/lang/Boolean;").__() }, { // new Boolean(false) = Boolean.FALSE (ignoring identity) ____.new_(BOOLEAN) .dup() .iconst_0() .invokespecial(BOOLEAN, "", "(Z)V").__(), ____.getstatic(BOOLEAN, "FALSE", "Ljava/lang/Boolean;").__() }, { // new Boolean(true) = Boolean.TRUE (ignoring identity) ____.new_(BOOLEAN) .dup() .iconst_1() .invokespecial(BOOLEAN, "", "(Z)V").__(), ____.getstatic(BOOLEAN, "TRUE", "Ljava/lang/Boolean;").__() }, { // new Boolean(v) = Boolean.valueof(v) (ignoring identity) ____.new_(BOOLEAN) .dup() .iload(A) .invokespecial(BOOLEAN, "", "(Z)V").__(), ____.iload(A) .invokestatic(BOOLEAN, "valueOf", "(Z)Ljava/lang/Boolean;").__() }, { // new Boolean(s) = Boolean.valueof(s) (ignoring identity) ____.new_(BOOLEAN) .dup() .getstatic(FIELD_Z) .invokespecial(BOOLEAN, "", "(Z)V").__(), ____.getstatic(FIELD_Z) .invokestatic(BOOLEAN, "valueOf", "(Z)Ljava/lang/Boolean;").__() }, { // new Boolean(v.f) = Boolean.valueof(v.f) (ignoring identity) ____.new_(BOOLEAN) .dup() .aload(A) .getfield(FIELD_Z) .invokespecial(BOOLEAN, "", "(Z)V").__(), ____.aload(A) .getfield(FIELD_Z) .invokestatic(BOOLEAN, "valueOf", "(Z)Ljava/lang/Boolean;").__() }, { // Boolean.FALSE.booleanValue() = false ____.getstatic(BOOLEAN, "FALSE", "Ljava/lang/Boolean;") .invokevirtual(BOOLEAN_VALUE).__(), ____.iconst_0().__() }, { // Boolean.TRUE.booleanValue() = true ____.getstatic(BOOLEAN, "TRUE", "Ljava/lang/Boolean;") .invokevirtual(BOOLEAN_VALUE).__(), ____.iconst_1().__() }, { // Boolean.valueOf(...).booleanValue() = nothing ____.invokestatic(BOOLEAN, "valueOf", "(Z)Ljava/lang/Boolean;") .invokevirtual(BOOLEAN_VALUE).__(), }, { // new Byte(B) = Byte.valueof(B) (ignoring identity) ____.new_(BYTE) .dup() .iconst(A) .invokespecial(BYTE, "", "(B)V").__(), ____.iconst(A) .invokestatic(BYTE, "valueOf", "(B)Ljava/lang/Byte;").__() }, { // new Byte(v) = Byte.valueof(v) (ignoring identity) ____.new_(BYTE) .dup() .iload(A) .invokespecial(BYTE, "", "(B)V").__(), ____.iload(A) .invokestatic(BYTE, "valueOf", "(B)Ljava/lang/Byte;").__() }, { // new Byte(s) = Byte.valueof(s) (ignoring identity) ____.new_(BYTE) .dup() .getstatic(FIELD_B) .invokespecial(BYTE, "", "(B)V").__(), ____.getstatic(FIELD_B) .invokestatic(BYTE, "valueOf", "(B)Ljava/lang/Byte;").__() }, { // new Byte(v.f) = Byte.valueof(v.f) (ignoring identity) ____.new_(BYTE) .dup() .aload(A) .getfield(FIELD_B) .invokespecial(BYTE, "", "(B)V").__(), ____.aload(A) .getfield(FIELD_B) .invokestatic(BYTE, "valueOf", "(B)Ljava/lang/Byte;").__() }, { // Byte.valueOf(...).byteValue() = nothing ____.invokestatic(BYTE, "valueOf", "(B)Ljava/lang/Byte;") .invokevirtual(BYTE_VALUE).__(), }, { // new Character(C) = Character.valueof(C) (ignoring identity) ____.new_(CHARACTER) .dup() .iconst(A) .invokespecial(CHARACTER, "", "(C)V").__(), ____.iconst(A) .invokestatic(CHARACTER, "valueOf", "(C)Ljava/lang/Character;").__() }, { // new Character(v) = Character.valueof(v) (ignoring identity) ____.new_(CHARACTER) .dup() .iload(A) .invokespecial(CHARACTER, "", "(C)V").__(), ____.iload(A) .invokestatic(CHARACTER, "valueOf", "(C)Ljava/lang/Character;").__() }, { // new Character(s) = Character.valueof(s) (ignoring identity) ____.new_(CHARACTER) .dup() .getstatic(FIELD_C) .invokespecial(CHARACTER, "", "(C)V").__(), ____.getstatic(FIELD_C) .invokestatic(CHARACTER, "valueOf", "(C)Ljava/lang/Character;").__() }, { // new Character(v.f) = Character.valueof(v.f) (ignoring identity) ____.new_(CHARACTER) .dup() .aload(A) .getfield(FIELD_C) .invokespecial(CHARACTER, "", "(C)V").__(), ____.aload(A) .getfield(FIELD_C) .invokestatic(CHARACTER, "valueOf", "(C)Ljava/lang/Character;").__() }, { // Character.valueOf(...).charValue() = nothing ____.invokestatic(CHARACTER, "valueOf", "(C)Ljava/lang/Character;") .invokevirtual(CHAR_VALUE).__(), }, { // new Short(S) = Short.valueof(S) (ignoring identity) ____.new_(SHORT) .dup() .iconst(A) .invokespecial(SHORT, "", "(S)V").__(), ____.iconst(A) .invokestatic(SHORT, "valueOf", "(S)Ljava/lang/Short;").__() }, { // new Short(v) = Short.valueof(v) (ignoring identity) ____.new_(SHORT) .dup() .iload(A) .invokespecial(SHORT, "", "(S)V").__(), ____.iload(A) .invokestatic(SHORT, "valueOf", "(S)Ljava/lang/Short;").__() }, { // new Short(s) = Short.valueof(s) (ignoring identity) ____.new_(SHORT) .dup() .getstatic(FIELD_S) .invokespecial(SHORT, "", "(S)V").__(), ____.getstatic(FIELD_S) .invokestatic(SHORT, "valueOf", "(S)Ljava/lang/Short;").__() }, { // new Short(v.f) = Short.valueof(v.f) (ignoring identity) ____.new_(SHORT) .dup() .aload(A) .getfield(FIELD_S) .invokespecial(SHORT, "", "(S)V").__(), ____.aload(A) .getfield(FIELD_S) .invokestatic(SHORT, "valueOf", "(S)Ljava/lang/Short;").__() }, { // Short.valueOf(...).shortValue() = nothing ____.invokestatic(SHORT, "valueOf", "(S)Ljava/lang/Short;") .invokevirtual(SHORT_VALUE).__(), }, { // new Integer(I) = Integer.valueof(I) (ignoring identity) ____.new_(INTEGER) .dup() .iconst(A) .invokespecial(INTEGER, "", "(I)V").__(), ____.iconst(A) .invokestatic(INTEGER, "valueOf", "(I)Ljava/lang/Integer;").__() }, { // new Integer(I) = Integer.valueof(I) (ignoring identity) ____.new_(INTEGER) .dup() .ldc_(A) .invokespecial(INTEGER, "", "(I)V").__(), ____.ldc_(A) .invokestatic(INTEGER, "valueOf", "(I)Ljava/lang/Integer;").__() }, { // new Integer(v) = Integer.valueof(v) (ignoring identity) ____.new_(INTEGER) .dup() .iload(A) .invokespecial(INTEGER, "", "(I)V").__(), ____.iload(A) .invokestatic(INTEGER, "valueOf", "(I)Ljava/lang/Integer;").__() }, { // new Integer(c) = Integer.valueof(c) (ignoring identity) ____.new_(INTEGER) .dup() .getstatic(FIELD_I) .invokespecial(INTEGER, "", "(I)V").__(), ____.getstatic(FIELD_I) .invokestatic(INTEGER, "valueOf", "(I)Ljava/lang/Integer;").__() }, { // new Integer(v.f) = Integer.valueof(v.f) (ignoring identity) ____.new_(INTEGER) .dup() .aload(A) .getfield(FIELD_I) .invokespecial(INTEGER, "", "(I)V").__(), ____.aload(A) .getfield(FIELD_I) .invokestatic(INTEGER, "valueOf", "(I)Ljava/lang/Integer;").__() }, { // Integer.valueOf(...).intValue() = nothing ____.invokestatic(INTEGER, "valueOf", "(I)Ljava/lang/Integer;") .invokevirtual(INT_VALUE).__(), }, { // new Float(F) = Float.valueof(F) (ignoring identity) ____.new_(FLOAT) .dup() .fconst(A) .invokespecial(FLOAT, "", "(F)V").__(), ____.fconst(A) .invokestatic(FLOAT, "valueOf", "(F)Ljava/lang/Float;").__() }, { // new Float(F) = Float.valueof(F) (ignoring identity) ____.new_(FLOAT) .dup() .ldc_(A) .invokespecial(FLOAT, "", "(F)V").__(), ____.ldc_(A) .invokestatic(FLOAT, "valueOf", "(F)Ljava/lang/Float;").__() }, { // new Float(v) = Float.valueof(v) (ignoring identity) ____.new_(FLOAT) .dup() .fload(A) .invokespecial(FLOAT, "", "(F)V").__(), ____.fload(A) .invokestatic(FLOAT, "valueOf", "(F)Ljava/lang/Float;").__() }, { // new Float(s) = Float.valueof(s) (ignoring identity) ____.new_(FLOAT) .dup() .getstatic(FIELD_F) .invokespecial(FLOAT, "", "(F)V").__(), ____.getstatic(FIELD_F) .invokestatic(FLOAT, "valueOf", "(F)Ljava/lang/Float;").__() }, { // new Float(v.f) = Float.valueof(v.f) (ignoring identity) ____.new_(FLOAT) .dup() .aload(A) .getfield(FIELD_F) .invokespecial(FLOAT, "", "(F)V").__(), ____.aload(A) .getfield(FIELD_F) .invokestatic(FLOAT, "valueOf", "(F)Ljava/lang/Float;").__() }, { // Float.valueOf(...).floatValue() = nothing ____.invokestatic(FLOAT, "valueOf", "(F)Ljava/lang/Float;") .invokevirtual(FLOAT_VALUE).__(), }, { // new Long(J) = Long.valueof(J) (ignoring identity) ____.new_(LONG) .dup() .lconst(A) .invokespecial(LONG, "", "(J)V").__(), ____.lconst(A) .invokestatic(LONG, "valueOf", "(J)Ljava/lang/Long;").__() }, { // new Long(J) = Long.valueof(J) (ignoring identity) ____.new_(LONG) .dup() .ldc2_w(A) .invokespecial(LONG, "", "(J)V").__(), ____.ldc2_w(A) .invokestatic(LONG, "valueOf", "(J)Ljava/lang/Long;").__() }, { // new Long(v) = Long.valueof(v) (ignoring identity) ____.new_(LONG) .dup() .iload(A) .invokespecial(LONG, "", "(J)V").__(), ____.iload(A) .invokestatic(LONG, "valueOf", "(J)Ljava/lang/Long;").__() }, { // new Long(s) = Long.valueof(s) (ignoring identity) ____.new_(LONG) .dup() .getstatic(FIELD_J) .invokespecial(LONG, "", "(J)V").__(), ____.getstatic(FIELD_J) .invokestatic(LONG, "valueOf", "(J)Ljava/lang/Long;").__() }, { // new Long(v.f) = Long.valueof(v.f) (ignoring identity) ____.new_(LONG) .dup() .aload(A) .getfield(FIELD_J) .invokespecial(LONG, "", "(J)V").__(), ____.aload(A) .getfield(FIELD_J) .invokestatic(LONG, "valueOf", "(J)Ljava/lang/Long;").__() }, { // Long.valueOf(...).longValue() = nothing ____.invokestatic(LONG, "valueOf", "(J)Ljava/lang/Long;") .invokevirtual(LONG_VALUE).__(), }, { // new Double(D) = Double.valueof(D) (ignoring identity) ____.new_(DOUBLE) .dup() .dconst(A) .invokespecial(DOUBLE, "", "(D)V").__(), ____.dconst(A) .invokestatic(DOUBLE, "valueOf", "(D)Ljava/lang/Double;").__() }, { // new Double(D) = Double.valueof(D) (ignoring identity) ____.new_(DOUBLE) .dup() .ldc2_w(A) .invokespecial(DOUBLE, "", "(D)V").__(), ____.ldc2_w(A) .invokestatic(DOUBLE, "valueOf", "(D)Ljava/lang/Double;").__() }, { // new Double(v) = Double.valueof(v) (ignoring identity) ____.new_(DOUBLE) .dup() .dload(A) .invokespecial(DOUBLE, "", "(D)V").__(), ____.dload(A) .invokestatic(DOUBLE, "valueOf", "(D)Ljava/lang/Double;").__() }, { // new Double(s) = Double.valueof(s) (ignoring identity) ____.new_(DOUBLE) .dup() .getstatic(FIELD_D) .invokespecial(DOUBLE, "", "(D)V").__(), ____.getstatic(FIELD_D) .invokestatic(DOUBLE, "valueOf", "(D)Ljava/lang/Double;").__() }, { // new Double(v.f) = Double.valueof(v.f) (ignoring identity) ____.new_(DOUBLE) .dup() .aload(A) .getfield(FIELD_D) .invokespecial(DOUBLE, "", "(D)V").__(), ____.aload(A) .getfield(FIELD_D) .invokestatic(DOUBLE, "valueOf", "(D)Ljava/lang/Double;").__() }, { // Double.valueOf(...).doubleValue() = nothing ____.invokestatic(DOUBLE, "valueOf", "(D)Ljava/lang/Double;") .invokevirtual(DOUBLE_VALUE).__(), }, // Doesn't fill out the references to the classes. //{ // ...class.getName() = "..." // ____.ldc_(A) // .invokevirtual(CLASS, "getName", "()Ljava/lang/String;").__(), // // ____.ldc_(CLASS_A_NAME).__() //}, //{ // ...class.getSimpleName() = "..." // ____.ldc_(A) // .invokevirtual(CLASS, "getSimpleName", "()Ljava/lang/String;").__(), // // ____.ldc_(CLASS_A_SIMPLE_NAME).__() //}, }; STRING_SEQUENCES = new Instruction[][][] { { // "...".equals("...") = true ____.ldc_(A) .ldc_(A) .invokevirtual(STRING, "equals", "(Ljava/lang/Object;)Z").__(), ____.iconst_1().__() }, { // "...".length() = ... ____.ldc_(A) .invokevirtual(STRING, "length", "()I").__(), ____.sipush(STRING_A_LENGTH).__() }, { // String.valueOf(Z) = ".... ____.iconst(A) .invokestatic(STRING, "valueOf", "(Z)Ljava/lang/String;").__(), ____.ldc_(BOOLEAN_A_STRING).__() }, { // String.valueOf(C) = "...." ____.iconst(A) .invokestatic(STRING, "valueOf", "(C)Ljava/lang/String;").__(), ____.ldc_(CHAR_A_STRING).__() }, { // String.valueOf(Cc) = "...." ____.ldc_(A) .invokestatic(STRING, "valueOf", "(C)Ljava/lang/String;").__(), ____.ldc_(CHAR_A_STRING).__() }, { // String.valueOf(I) = "...." ____.iconst(A) .invokestatic(STRING, "valueOf", "(I)Ljava/lang/String;").__(), ____.ldc_(INT_A_STRING).__() }, { // String.valueOf(Ic) = "...." ____.ldc_(A) .invokestatic(STRING, "valueOf", "(I)Ljava/lang/String;").__(), ____.ldc_(INT_A_STRING).__() }, { // String.valueOf(J) = "...." ____.lconst(A) .invokestatic(STRING, "valueOf", "(J)Ljava/lang/String;").__(), ____.ldc_(LONG_A_STRING).__() }, { // String.valueOf(Jc) = "...." ____.ldc2_w(A) .invokestatic(STRING, "valueOf", "(J)Ljava/lang/String;").__(), ____.ldc_(LONG_A_STRING).__() }, { // String.valueOf(F) = "...." ____.fconst(A) .invokestatic(STRING, "valueOf", "(F)Ljava/lang/String;").__(), ____.ldc_(FLOAT_A_STRING).__() }, { // String.valueOf(Fc) = "...." ____.ldc_(A) .invokestatic(STRING, "valueOf", "(F)Ljava/lang/String;").__(), ____.ldc_(FLOAT_A_STRING).__() }, { // String.valueOf(D) = "...." ____.dconst(A) .invokestatic(STRING, "valueOf", "(D)Ljava/lang/String;").__(), ____.ldc_(DOUBLE_A_STRING).__() }, { // String.valueOf(Dc) = "...." ____.ldc2_w(A) .invokestatic(STRING, "valueOf", "(D)Ljava/lang/String;").__(), ____.ldc_(DOUBLE_A_STRING).__() }, { // "...".concat("...") = "......" ____.ldc_(A) .ldc_(B) .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__(), ____.ldc_(STRING_A_STRING | STRING_B_STRING).__(), }, { // new StringBuffer("...").toString() = "..." (ignoring identity) ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .invokevirtual(TO_STRING).__(), ____.ldc_(A).__() }, { // new StringBuffer(string).toString() = string (ignoring identity and discarding any NullPointerException) ____.new_(STRING_BUFFER) .dup() .aload(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .invokevirtual(TO_STRING).__(), ____.aload(A).__() }, { // new StringBuffer("...").length() = length ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .invokevirtual(STRING_BUFFER, "length", "()I").__(), ____.sipush(STRING_A_LENGTH).__() }, { // new StringBuffer() (without dup) = nothing ____.new_(STRING_BUFFER) .ldc_(A) .invokespecial(STRING_BUFFER, "", "()V").__(), }, { // new StringBuffer("...") (without dup) = nothing ____.new_(STRING_BUFFER) .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__(), }, { // new StringBuffer()/pop = nothing ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "()V") .pop().__(), }, { // new StringBuffer("...")/pop = nothing ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .pop().__(), }, { // new StringBuffer("...").append(z)/pop = nothing ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUFFER, "append", "(Z)Ljava/lang/StringBuffer;") .pop().__(), }, { // new StringBuffer("...").append(c)/pop = nothing ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUFFER, "append", "(C)Ljava/lang/StringBuffer;") .pop().__(), }, { // new StringBuffer("...").append(i)/pop = nothing ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUFFER, "append", "(I)Ljava/lang/StringBuffer;") .pop().__(), }, { // new StringBuffer("...").append(l)/pop = nothing ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .lload(B) .invokevirtual(STRING_BUFFER, "append", "(J)Ljava/lang/StringBuffer;") .pop().__(), }, { // new StringBuffer("...").append(f)/pop = nothing ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .fload(B) .invokevirtual(STRING_BUFFER, "append", "(F)Ljava/lang/StringBuffer;") .pop().__(), }, { // new StringBuffer("...").append(d)/pop = nothing ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .dload(B) .invokevirtual(STRING_BUFFER, "append", "(D)Ljava/lang/StringBuffer;") .pop().__(), }, { // new StringBuffer("...").append(s)/pop = nothing ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .aload(B) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .pop().__(), }, { // StringBuffer#toString()/pop = pop ____.invokevirtual(TO_STRING) .pop().__(), ____.pop().__() }, { // StringBuffer#append("") = nothing ____.ldc("") .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__(), }, { // new StringBuffer().append(Z) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .iconst(A) .invokevirtual(STRING_BUFFER, "append", "(Z)Ljava/lang/StringBuffer;").__(), ____.ldc_(BOOLEAN_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append(C) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .iconst(A) .invokevirtual(STRING_BUFFER, "append", "(C)Ljava/lang/StringBuffer;").__(), ____.ldc_(CHAR_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append(Cc) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(C)Ljava/lang/StringBuffer;").__(), ____.ldc_(CHAR_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append(I) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .iconst(A) .invokevirtual(STRING_BUFFER, "append", "(I)Ljava/lang/StringBuffer;").__(), ____.ldc_(INT_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append(Ic) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(I)Ljava/lang/StringBuffer;").__(), ____.ldc_(INT_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append(J) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .lconst(A) .invokevirtual(STRING_BUFFER, "append", "(J)Ljava/lang/StringBuffer;").__(), ____.ldc_(LONG_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append(Jc) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .ldc2_w(A) .invokevirtual(STRING_BUFFER, "append", "(J)Ljava/lang/StringBuffer;").__(), ____.ldc_(LONG_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append(F) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .fconst(A) .invokevirtual(STRING_BUFFER, "append", "(F)Ljava/lang/StringBuffer;").__(), ____.ldc_(FLOAT_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append(Fc) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(F)Ljava/lang/StringBuffer;").__(), ____.ldc_(FLOAT_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append(D) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .dconst(A) .invokevirtual(STRING_BUFFER, "append", "(D)Ljava/lang/StringBuffer;").__(), ____.ldc_(DOUBLE_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append(Dc) = new StringBuffer("....") ____.invokespecial(STRING_BUFFER, "", "()V") .ldc2_w(A) .invokevirtual(STRING_BUFFER, "append", "(D)Ljava/lang/StringBuffer;").__(), ____.ldc_(DOUBLE_A_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer().append("...") = new StringBuffer("...") ____.invokespecial(STRING_BUFFER, "", "()V") .ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__(), ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(Z) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .iconst(B) .invokevirtual(STRING_BUFFER, "append", "(Z)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | BOOLEAN_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(C) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .iconst(B) .invokevirtual(STRING_BUFFER, "append", "(C)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | CHAR_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(Cc) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .ldc_(B) .invokevirtual(STRING_BUFFER, "append", "(C)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | CHAR_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(I) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .iconst(B) .invokevirtual(STRING_BUFFER, "append", "(I)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | INT_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(Ic) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .ldc_(B) .invokevirtual(STRING_BUFFER, "append", "(I)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | INT_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(J) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .lconst(B) .invokevirtual(STRING_BUFFER, "append", "(J)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | LONG_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(Jc) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .ldc2_w(B) .invokevirtual(STRING_BUFFER, "append", "(J)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | LONG_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(F) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .fconst(B) .invokevirtual(STRING_BUFFER, "append", "(F)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | FLOAT_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(Fc) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .ldc_(B) .invokevirtual(STRING_BUFFER, "append", "(F)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | FLOAT_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(D) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .dconst(B) .invokevirtual(STRING_BUFFER, "append", "(D)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | DOUBLE_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(Dc) = new StringBuffer("....") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .ldc2_w(B) .invokevirtual(STRING_BUFFER, "append", "(D)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | DOUBLE_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append("...") = new StringBuffer("......") ____.ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .ldc_(B) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | STRING_B_STRING) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuffer("...").append(z).toString() = "...".concat(String.valueOf(z)) ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUFFER, "append", "(Z)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .iload(B) .invokestatic(STRING, "valueOf", "(Z)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuffer("...").append(c).toString() = "...".concat(String.valueOf(c)) ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUFFER, "append", "(C)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .iload(B) .invokestatic(STRING, "valueOf", "(C)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuffer("...").append(i).toString() = "...".concat(String.valueOf(i)) ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUFFER, "append", "(I)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .iload(B) .invokestatic(STRING, "valueOf", "(I)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuffer("...").append(l).toString() = "...".concat(String.valueOf(l)) ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .lload(B) .invokevirtual(STRING_BUFFER, "append", "(J)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .lload(B) .invokestatic(STRING, "valueOf", "(J)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuffer("...").append(f).toString() = "...".concat(String.valueOf(f)) ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .fload(B) .invokevirtual(STRING_BUFFER, "append", "(F)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .fload(B) .invokestatic(STRING, "valueOf", "(F)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuffer("...").append(d).toString() = "...".concat(String.valueOf(d)) ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .dload(B) .invokevirtual(STRING_BUFFER, "append", "(D)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .dload(B) .invokestatic(STRING, "valueOf", "(D)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuffer("...").append(string).toString() = "...".concat(String.valueOf(string)) ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .aload(B) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .aload(B) .invokestatic(STRING, "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuffer("...").append(object).toString() = "...".concat(String.valueOf(object)) ____.new_(STRING_BUFFER) .dup() .ldc_(A) .invokespecial(STRING_BUFFER, "", "(Ljava/lang/String;)V") .aload(B) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .aload(B) .invokestatic(STRING, "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // StringBuffer#append("...").append(Z) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .iconst(B) .invokevirtual(STRING_BUFFER, "append", "(Z)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | BOOLEAN_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append(C) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .iconst(B) .invokevirtual(STRING_BUFFER, "append", "(C)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | CHAR_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append(Cc) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .ldc_(B) .invokevirtual(STRING_BUFFER, "append", "(C)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | CHAR_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append(I) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .iconst(B) .invokevirtual(STRING_BUFFER, "append", "(I)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | INT_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append(Ic) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .ldc_(B) .invokevirtual(STRING_BUFFER, "append", "(I)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | INT_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append(J) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .lconst(B) .invokevirtual(STRING_BUFFER, "append", "(J)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | LONG_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append(Jc) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .ldc2_w(B) .invokevirtual(STRING_BUFFER, "append", "(J)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | LONG_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append(F) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .fconst(B) .invokevirtual(STRING_BUFFER, "append", "(F)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | FLOAT_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append(Fc) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .ldc_(B) .invokevirtual(STRING_BUFFER, "append", "(F)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | FLOAT_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append(D) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .dconst(B) .invokevirtual(STRING_BUFFER, "append", "(D)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | DOUBLE_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append(Dc) = StringBuffer#append("....") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .ldc2_w(B) .invokevirtual(STRING_BUFFER, "append", "(D)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | DOUBLE_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // StringBuffer#append("...").append("...") = StringBuffer#append("......") ____.ldc_(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .ldc_(B) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__(), ____.ldc_(STRING_A_STRING | STRING_B_STRING) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;").__() }, { // new StringBuffer().append(z).toString() = String.valueOf(z) ____.new_(STRING_BUFFER) .dup() .invokespecial(STRING_BUFFER, "", "()V") .iload(A) .invokevirtual(STRING_BUFFER, "append", "(Z)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.iload(A) .invokestatic(STRING, "valueOf", "(Z)Ljava/lang/String;").__() }, { // new StringBuffer().append(c).toString() = String.valueOf(c) ____.new_(STRING_BUFFER) .dup() .invokespecial(STRING_BUFFER, "", "()V") .iload(A) .invokevirtual(STRING_BUFFER, "append", "(C)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.iload(A) .invokestatic(STRING, "valueOf", "(C)Ljava/lang/String;").__() }, { // new StringBuffer().append(i).toString() = String.valueOf(i) ____.new_(STRING_BUFFER) .dup() .invokespecial(STRING_BUFFER, "", "()V") .iload(A) .invokevirtual(STRING_BUFFER, "append", "(I)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.iload(A) .invokestatic(STRING, "valueOf", "(I)Ljava/lang/String;").__() }, { // new StringBuffer().append(j).toString() = String.valueOf(j) ____.new_(STRING_BUFFER) .dup() .invokespecial(STRING_BUFFER, "", "()V") .lload(A) .invokevirtual(STRING_BUFFER, "append", "(J)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.lload(A) .invokestatic(STRING, "valueOf", "(J)Ljava/lang/String;").__() }, { // new StringBuffer().append(f).toString() = String.valueOf(f) ____.new_(STRING_BUFFER) .dup() .invokespecial(STRING_BUFFER, "", "()V") .fload(A) .invokevirtual(STRING_BUFFER, "append", "(F)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.fload(A) .invokestatic(STRING, "valueOf", "(F)Ljava/lang/String;").__() }, { // new StringBuffer().append(d).toString() = String.valueOf(d) ____.new_(STRING_BUFFER) .dup() .invokespecial(STRING_BUFFER, "", "()V") .dload(A) .invokevirtual(STRING_BUFFER, "append", "(D)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.dload(A) .invokestatic(STRING, "valueOf", "(D)Ljava/lang/String;").__() }, { // new StringBuffer().append(string).toString() = String.valueOf(string) ____.new_(STRING_BUFFER) .dup() .invokespecial(STRING_BUFFER, "", "()V") .aload(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.aload(A) .invokestatic(STRING, "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;").__() }, { // new StringBuffer().append(object).toString() = String.valueOf(object) ____.new_(STRING_BUFFER) .dup() .invokespecial(STRING_BUFFER, "", "()V") .aload(A) .invokevirtual(STRING_BUFFER, "append", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;") .invokevirtual(TO_STRING).__(), ____.aload(A) .invokestatic(STRING, "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;").__() }, { // new StringBuilder("...").toString() = "..." (ignoring identity) ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .invokevirtual(TO_STRING).__(), ____.ldc_(A).__() }, { // new StringBuilder(string).toString() = string (ignoring identity and discarding any NullPointerException) ____.new_(STRING_BUILDER) .dup() .aload(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .invokevirtual(TO_STRING).__(), ____.aload(A).__() }, { // new StringBuilder("...").length() = length ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .invokevirtual(STRING_BUILDER, "length", "()I").__(), ____.sipush(STRING_A_LENGTH).__() }, { // new StringBuilder() (without dup) = nothing ____.new_(STRING_BUILDER) .ldc_(A) .invokespecial(STRING_BUILDER, "", "()V").__(), }, { // new StringBuilder("...") (without dup) = nothing ____.new_(STRING_BUILDER) .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__(), }, { // new StringBuilder()/pop = nothing ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "()V") .pop().__(), }, { // new StringBuilder("...")/pop = nothing ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .pop().__(), }, { // new StringBuilder("...").append(z)/pop = nothing ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUILDER, "append", "(Z)Ljava/lang/StringBuilder;") .pop().__(), }, { // new StringBuilder("...").append(c)/pop = nothing ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUILDER, "append", "(C)Ljava/lang/StringBuilder;") .pop().__(), }, { // new StringBuilder("...").append(i)/pop = nothing ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUILDER, "append", "(I)Ljava/lang/StringBuilder;") .pop().__(), }, { // new StringBuilder("...").append(l)/pop = nothing ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .lload(B) .invokevirtual(STRING_BUILDER, "append", "(J)Ljava/lang/StringBuilder;") .pop().__(), }, { // new StringBuilder("...").append(f)/pop = nothing ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .fload(B) .invokevirtual(STRING_BUILDER, "append", "(F)Ljava/lang/StringBuilder;") .pop().__(), }, { // new StringBuilder("...").append(d)/pop = nothing ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .dload(B) .invokevirtual(STRING_BUILDER, "append", "(D)Ljava/lang/StringBuilder;") .pop().__(), }, { // new StringBuilder("...").append(s)/pop = nothing ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .aload(B) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .pop().__(), }, { // StringBuilder#toString()/pop = pop ____.invokevirtual(TO_STRING) .pop().__(), ____.pop().__() }, { // StringBuilder#append("") = nothing ____.ldc("") .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__(), }, { // new StringBuilder().append(Z) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .iconst(A) .invokevirtual(STRING_BUILDER, "append", "(Z)Ljava/lang/StringBuilder;").__(), ____.ldc_(BOOLEAN_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append(C) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .iconst(A) .invokevirtual(STRING_BUILDER, "append", "(C)Ljava/lang/StringBuilder;").__(), ____.ldc_(CHAR_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append(Cc) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(C)Ljava/lang/StringBuilder;").__(), ____.ldc_(CHAR_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append(I) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .iconst(A) .invokevirtual(STRING_BUILDER, "append", "(I)Ljava/lang/StringBuilder;").__(), ____.ldc_(INT_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append(Ic) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(I)Ljava/lang/StringBuilder;").__(), ____.ldc_(INT_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append(J) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .lconst(A) .invokevirtual(STRING_BUILDER, "append", "(J)Ljava/lang/StringBuilder;").__(), ____.ldc_(LONG_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append(Jc) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .ldc2_w(A) .invokevirtual(STRING_BUILDER, "append", "(J)Ljava/lang/StringBuilder;").__(), ____.ldc_(LONG_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append(F) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .fconst(A) .invokevirtual(STRING_BUILDER, "append", "(F)Ljava/lang/StringBuilder;").__(), ____.ldc_(FLOAT_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append(Fc) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(F)Ljava/lang/StringBuilder;").__(), ____.ldc_(FLOAT_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append(D) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .dconst(A) .invokevirtual(STRING_BUILDER, "append", "(D)Ljava/lang/StringBuilder;").__(), ____.ldc_(DOUBLE_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append(Dc) = new StringBuilder("....") ____.invokespecial(STRING_BUILDER, "", "()V") .ldc2_w(A) .invokevirtual(STRING_BUILDER, "append", "(D)Ljava/lang/StringBuilder;").__(), ____.ldc_(DOUBLE_A_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder().append("...") = new StringBuilder("...") ____.invokespecial(STRING_BUILDER, "", "()V") .ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__(), ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(Z) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .iconst(B) .invokevirtual(STRING_BUILDER, "append", "(Z)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | BOOLEAN_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(C) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .iconst(B) .invokevirtual(STRING_BUILDER, "append", "(C)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | CHAR_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(Cc) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .ldc_(B) .invokevirtual(STRING_BUILDER, "append", "(C)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | CHAR_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(I) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .iconst(B) .invokevirtual(STRING_BUILDER, "append", "(I)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | INT_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(Ic) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .ldc_(B) .invokevirtual(STRING_BUILDER, "append", "(I)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | INT_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(J) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .lconst(B) .invokevirtual(STRING_BUILDER, "append", "(J)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | LONG_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(Jc) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .ldc2_w(B) .invokevirtual(STRING_BUILDER, "append", "(J)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | LONG_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(F) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .fconst(B) .invokevirtual(STRING_BUILDER, "append", "(F)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | FLOAT_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(Fc) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .ldc_(B) .invokevirtual(STRING_BUILDER, "append", "(F)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | FLOAT_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(D) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .dconst(B) .invokevirtual(STRING_BUILDER, "append", "(D)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | DOUBLE_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(Dc) = new StringBuilder("....") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .ldc2_w(B) .invokevirtual(STRING_BUILDER, "append", "(D)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | DOUBLE_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append("...") = new StringBuilder("......") ____.ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .ldc_(B) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | STRING_B_STRING) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V").__() }, { // new StringBuilder("...").append(z).toString() = "...".concat(String.valueOf(z)) ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUILDER, "append", "(Z)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .iload(B) .invokestatic(STRING, "valueOf", "(Z)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuilder("...").append(c).toString() = "...".concat(String.valueOf(c)) ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUILDER, "append", "(C)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .iload(B) .invokestatic(STRING, "valueOf", "(C)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuilder("...").append(i).toString() = "...".concat(String.valueOf(i)) ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .iload(B) .invokevirtual(STRING_BUILDER, "append", "(I)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .iload(B) .invokestatic(STRING, "valueOf", "(I)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuilder("...").append(l).toString() = "...".concat(String.valueOf(l)) ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .lload(B) .invokevirtual(STRING_BUILDER, "append", "(J)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .lload(B) .invokestatic(STRING, "valueOf", "(J)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuilder("...").append(f).toString() = "...".concat(String.valueOf(f)) ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .fload(B) .invokevirtual(STRING_BUILDER, "append", "(F)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .fload(B) .invokestatic(STRING, "valueOf", "(F)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuilder("...").append(d).toString() = "...".concat(String.valueOf(d)) ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .dload(B) .invokevirtual(STRING_BUILDER, "append", "(D)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .dload(B) .invokestatic(STRING, "valueOf", "(D)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuilder("...").append(string).toString() = "...".concat(String.valueOf(string)) ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .aload(B) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .aload(B) .invokestatic(STRING, "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // new StringBuilder("...").append(object).toString() = "...".concat(String.valueOf(object)) ____.new_(STRING_BUILDER) .dup() .ldc_(A) .invokespecial(STRING_BUILDER, "", "(Ljava/lang/String;)V") .aload(B) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.ldc_(A) .aload(B) .invokestatic(STRING, "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;") .invokevirtual(STRING, "concat", "(Ljava/lang/String;)Ljava/lang/String;").__() }, { // StringBuilder#append("...").append(Z) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .iconst(B) .invokevirtual(STRING_BUILDER, "append", "(Z)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | BOOLEAN_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append(C) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .iconst(B) .invokevirtual(STRING_BUILDER, "append", "(C)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | CHAR_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append(Cc) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .ldc_(B) .invokevirtual(STRING_BUILDER, "append", "(C)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | CHAR_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append(I) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .iconst(B) .invokevirtual(STRING_BUILDER, "append", "(I)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | INT_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append(Ic) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .ldc_(B) .invokevirtual(STRING_BUILDER, "append", "(I)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | INT_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append(J) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .lconst(B) .invokevirtual(STRING_BUILDER, "append", "(J)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | LONG_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append(Jc) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .ldc2_w(B) .invokevirtual(STRING_BUILDER, "append", "(J)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | LONG_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append(F) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .fconst(B) .invokevirtual(STRING_BUILDER, "append", "(F)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | FLOAT_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append(Fc) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .ldc_(B) .invokevirtual(STRING_BUILDER, "append", "(F)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | FLOAT_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append(D) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .dconst(B) .invokevirtual(STRING_BUILDER, "append", "(D)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | DOUBLE_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append(Dc) = StringBuilder#append("....") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .ldc2_w(B) .invokevirtual(STRING_BUILDER, "append", "(D)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | DOUBLE_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // StringBuilder#append("...").append("...") = StringBuilder#append("......") ____.ldc_(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .ldc_(B) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__(), ____.ldc_(STRING_A_STRING | STRING_B_STRING) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;").__() }, { // new StringBuilder().append(z).toString() = String.valueOf(z) ____.new_(STRING_BUILDER) .dup() .invokespecial(STRING_BUILDER, "", "()V") .iload(A) .invokevirtual(STRING_BUILDER, "append", "(Z)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.iload(A) .invokestatic(STRING, "valueOf", "(Z)Ljava/lang/String;").__() }, { // new StringBuilder().append(c).toString() = String.valueOf(c) ____.new_(STRING_BUILDER) .dup() .invokespecial(STRING_BUILDER, "", "()V") .iload(A) .invokevirtual(STRING_BUILDER, "append", "(C)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.iload(A) .invokestatic(STRING, "valueOf", "(C)Ljava/lang/String;").__() }, { // new StringBuilder().append(i).toString() = String.valueOf(i) ____.new_(STRING_BUILDER) .dup() .invokespecial(STRING_BUILDER, "", "()V") .iload(A) .invokevirtual(STRING_BUILDER, "append", "(I)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.iload(A) .invokestatic(STRING, "valueOf", "(I)Ljava/lang/String;").__() }, { // new StringBuilder().append(j).toString() = String.valueOf(j) ____.new_(STRING_BUILDER) .dup() .invokespecial(STRING_BUILDER, "", "()V") .lload(A) .invokevirtual(STRING_BUILDER, "append", "(J)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.lload(A) .invokestatic(STRING, "valueOf", "(J)Ljava/lang/String;").__() }, { // new StringBuilder().append(f).toString() = String.valueOf(f) ____.new_(STRING_BUILDER) .dup() .invokespecial(STRING_BUILDER, "", "()V") .fload(A) .invokevirtual(STRING_BUILDER, "append", "(F)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.fload(A) .invokestatic(STRING, "valueOf", "(F)Ljava/lang/String;").__() }, { // new StringBuilder().append(d).toString() = String.valueOf(d) ____.new_(STRING_BUILDER) .dup() .invokespecial(STRING_BUILDER, "", "()V") .dload(A) .invokevirtual(STRING_BUILDER, "append", "(D)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.dload(A) .invokestatic(STRING, "valueOf", "(D)Ljava/lang/String;").__() }, { // new StringBuilder().append(string).toString() = String.valueOf(string) ____.new_(STRING_BUILDER) .dup() .invokespecial(STRING_BUILDER, "", "()V") .aload(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.aload(A) .invokestatic(STRING, "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;").__() }, { // new StringBuilder().append(object).toString() = String.valueOf(object) ____.new_(STRING_BUILDER) .dup() .invokespecial(STRING_BUILDER, "", "()V") .aload(A) .invokevirtual(STRING_BUILDER, "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;") .invokevirtual(TO_STRING).__(), ____.aload(A) .invokestatic(STRING, "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;").__() }, }; MATH_SEQUENCES = new Instruction[][][] { { // (float)Math.abs((double)...) = Math.abs(...) ____.f2d() .invokestatic(MATH, "abs", "(D)D") .d2f().__(), ____.invokestatic(MATH, "abs", "(F)F").__() }, { // (float)Math.abs(...) = Math.abs((float)...) ____.invokestatic(MATH, "abs", "(D)D") .d2f().__(), ____.d2f() .invokestatic(MATH, "abs", "(F)F").__() }, { // (int)Math.floor((double)...) = ... ____.i2d() .invokestatic(MATH, "floor", "(D)D") .d2i().__(), }, { // (int)Math.ceil((double)...) = ... ____.i2d() .invokestatic(MATH, "ceil", "(D)D") .d2i().__(), }, { // (float)Math.min((double)..., 0.0) = Math.min(..., 0f) ____.f2d() .dconst_0() .invokestatic(MATH, "min", "(DD)D") .d2f().__(), ____.fconst_0() .invokestatic(MATH, "min", "(FF)F").__() }, { // (float)Math.min(..., 0.0) = Math.min((float)..., 0f) (assuming in float range) ____.dconst_0() .invokestatic(MATH, "min", "(DD)D") .d2f().__(), ____.d2f() .fconst_0() .invokestatic(MATH, "min", "(FF)F").__() }, { // (float)Math.max((double)..., 0.0) = Math.max(..., 0f) ____.f2d() .dconst_0() .invokestatic(MATH, "max", "(DD)D") .d2f().__(), ____.fconst_0() .invokestatic(MATH, "max", "(FF)F").__() }, { // (float)Math.max(..., 0.0) = Math.max((float)..., 0f) (assuming in float range) ____.dconst_0() .invokestatic(MATH, "max", "(DD)D") .d2f().__(), ____.d2f() .fconst_0() .invokestatic(MATH, "max", "(FF)F").__() }, }; MATH_ANDROID_SEQUENCES = new Instruction[][][] { // As of API level 22, FloatMath has been deprecated, as the // equivalent methods in Math are faster on Android versions // with a JIT. We therefore now convert from FloatMath to Math. { // FloatMath.sqrt((float)...) = (float)Math.sqrt(...) ____.d2f() .invokestatic(FLOAT_MATH, "sqrt", "(F)F").__(), ____.invokestatic(MATH, "sqrt", "(D)D") .d2f().__() }, { // FloatMath.sqrt(...) = (float)Math.sqrt((double)...) ____.invokestatic(FLOAT_MATH, "sqrt", "(F)F").__(), ____.f2d() .invokestatic(MATH, "sqrt", "(D)D") .d2f().__() }, { // FloatMath.cos((float)...) = (float)Math.cos(...) ____.d2f() .invokestatic(FLOAT_MATH, "cos", "(F)F").__(), ____.invokestatic(MATH, "cos", "(D)D") .d2f().__() }, { // FloatMath.cos(...) = (float)Math.cos((double)...) ____.invokestatic(FLOAT_MATH, "cos", "(F)F").__(), ____.f2d() .invokestatic(MATH, "cos", "(D)D") .d2f().__() }, { // FloatMath.sin((float)...) = (float)Math.sin(...) ____.d2f() .invokestatic(FLOAT_MATH, "sin", "(F)F").__(), ____.invokestatic(MATH, "sin", "(D)D") .d2f().__() }, { // FloatMath.sin(...) = (float)Math.sin((double)...) ____.invokestatic(FLOAT_MATH, "sin", "(F)F").__(), ____.f2d() .invokestatic(MATH, "sin", "(D)D") .d2f().__() }, { // FloatMath.floor((float)...) = (float)Math.floor(...) ____.d2f() .invokestatic(FLOAT_MATH, "floor", "(F)F").__(), ____.invokestatic(MATH, "floor", "(D)D") .d2f().__() }, { // FloatMath.floor(...) = (float)Math.floor((double)...) ____.invokestatic(FLOAT_MATH, "floor", "(F)F").__(), ____.f2d() .invokestatic(MATH, "floor", "(D)D") .d2f().__() }, { // FloatMath.ceil((float)...) = (float)Math.ceil(...) ____.d2f() .invokestatic(FLOAT_MATH, "ceil", "(F)F").__(), ____.invokestatic(MATH, "ceil", "(D)D") .d2f().__() }, { // FloatMath.ceil(...) = (float)Math.ceil((double)...) ____.invokestatic(FLOAT_MATH, "ceil", "(F)F").__(), ____.f2d() .invokestatic(MATH, "ceil", "(D)D") .d2f().__() }, }; CONSTANTS = ____.constants(); } /** * Prints out the instruction sequences. */ public static void main(String[] args) { InstructionSequenceConstants instructionSequenceConstants = new InstructionSequenceConstants(new ClassPool(), new ClassPool()); Instruction[][][][] sets = new Instruction[][][][] { instructionSequenceConstants.VARIABLE_SEQUENCES, instructionSequenceConstants.ARITHMETIC_SEQUENCES, instructionSequenceConstants.FIELD_SEQUENCES, instructionSequenceConstants.CAST_SEQUENCES, instructionSequenceConstants.BRANCH_SEQUENCES, instructionSequenceConstants.STRING_SEQUENCES, instructionSequenceConstants.OBJECT_SEQUENCES, instructionSequenceConstants.MATH_SEQUENCES, instructionSequenceConstants.MATH_ANDROID_SEQUENCES, }; ProgramClass clazz = new ProgramClass(); clazz.constantPool = instructionSequenceConstants.CONSTANTS; for (int setIndex = 0; setIndex < sets.length; setIndex++) { Instruction[][][] sequencePairs = sets[setIndex]; for (int sequencePairIndex = 0; sequencePairIndex < sequencePairs.length; sequencePairIndex++) { Instruction[][] sequencePair = sequencePairs[sequencePairIndex]; // Print out the pattern instructions. Instruction[] sequence = sequencePair[0]; for (int index = 0; index < sequence.length; index++) { Instruction instruction = sequence[index]; try { instruction.accept(clazz, null, null, index, new ClassPrinter()); } catch (Exception e) {} } // Are there any replacement instructions? if (sequencePair.length < 2) { System.out.println("=> delete"); } else { System.out.println("=>"); // Print out the replacement instructions. sequence = sequencePair[1]; for (int index = 0; index < sequence.length; index++) { Instruction instruction = sequence[index]; try { instruction.accept(clazz, null, null, index, new ClassPrinter()); } catch (Exception e) {} } } System.out.println(); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/LineNumberLinearizer.java ================================================ /* * 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.peephole; import proguard.AppView; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramMethod; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.ExtendedLineNumberInfo; import proguard.classfile.attribute.LineNumberInfo; import proguard.classfile.attribute.LineNumberTableAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AllLineNumberInfoVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.attribute.visitor.LineNumberInfoVisitor; import proguard.classfile.attribute.visitor.LineNumberRangeFinder; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.MemberVisitor; import proguard.pass.Pass; import java.util.Stack; /** * This pass disambiguates line numbers, in the classes that it * visits. It shifts line numbers that originate from different classes * (e.g. due to method inlining or class merging) to blocks that don't * overlap with the main line numbers and with each other. The line numbers * then uniquely identify the inlined and merged code in the classes. * * @author Eric Lafortune */ public class LineNumberLinearizer implements Pass, ClassVisitor, MemberVisitor, AttributeVisitor, LineNumberInfoVisitor { private static final Logger logger = LogManager.getLogger(LineNumberLinearizer.class); public static final int SHIFT_ROUNDING = 1000; private static final int SHIFT_ROUNDING_LIMIT = 50000; private final Stack enclosingLineNumbers = new Stack<>(); private LineNumberInfo previousLineNumberInfo; private int highestUsedLineNumber; private int currentLineNumberShift; /** * Disambiguates the line numbers of all program classes, after * optimizations like method inlining and class merging. */ @Override public void execute(AppView appView) { appView.programClassPool.classesAccept(this); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Find the highest line number in the entire class. LineNumberRangeFinder lineNumberRangeFinder = new LineNumberRangeFinder(); programClass.methodsAccept(new AllAttributeVisitor(true, new AllLineNumberInfoVisitor( lineNumberRangeFinder))); // Are there any inlined line numbers? if (lineNumberRangeFinder.hasSource()) { // Remember the minimum initial shift. highestUsedLineNumber = lineNumberRangeFinder.getHighestLineNumber(); // Shift the inlined line numbers. programClass.methodsAccept(this); } } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { programMethod.attributesAccept(programClass, this); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttribute.attributesAccept(clazz, method, this); } public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) { logger.debug("LineNumberLinearizer [{}.{}{}]:", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz) ); enclosingLineNumbers.clear(); previousLineNumberInfo = null; // Process all line numbers. lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this); } // Implementations for LineNumberInfoVisitor. public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo) { String source = lineNumberInfo.getSource(); String debugMessage = String.format(" [%s] line %s%s", lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber, source == null ? "" : " [" + source + "]" ); // Is it an inlined line number? if (source != null) { ExtendedLineNumberInfo extendedLineNumberInfo = (ExtendedLineNumberInfo)lineNumberInfo; int lineNumber = extendedLineNumberInfo.u2lineNumber; // Are we entering or exiting a new inlined block? if (previousLineNumberInfo == null || !source.equals(previousLineNumberInfo.getSource())) { // Are we entering a new inlined block? if (lineNumber != MethodInliner.INLINED_METHOD_END_LINE_NUMBER) { // Remember information about the inlined block. enclosingLineNumbers.push(previousLineNumberInfo != null ? new MyLineNumberBlock(currentLineNumberShift, previousLineNumberInfo.u2lineNumber, previousLineNumberInfo.getSource()) : new MyLineNumberBlock(0, 0, null)); // Parse the end line number from the source string, // so we know how large a block this will be. int separatorIndex1 = source.indexOf(':'); int separatorIndex2 = source.indexOf(':', separatorIndex1 + 1); int startLineNumber = Integer.parseInt(source.substring(separatorIndex1 + 1, separatorIndex2)); int endLineNumber = Integer.parseInt(source.substring(separatorIndex2 + 1)); // Start shifting, if necessary, so the block ends up beyond // the highest used line number. We're striving for rounded // shifts, unless we've reached a given limit, to avoid // running out of line numbers too quickly. currentLineNumberShift = highestUsedLineNumber > SHIFT_ROUNDING_LIMIT ? highestUsedLineNumber - startLineNumber + 1 : startLineNumber > highestUsedLineNumber ? 0 : (highestUsedLineNumber - startLineNumber + SHIFT_ROUNDING) / SHIFT_ROUNDING * SHIFT_ROUNDING; highestUsedLineNumber = endLineNumber + currentLineNumberShift; debugMessage += String.format(" (enter with shift %s)", currentLineNumberShift); // Apply the shift. lineNumberInfo.u2lineNumber += currentLineNumberShift; } // TODO: There appear to be cases where the stack is empty at this point, so we've added a check. else if (enclosingLineNumbers.isEmpty()) { debugMessage += String.format("Problem linearizing line numbers for optimized code %s.%s)", clazz.getName(), method.getName(clazz)); logger.debug(debugMessage); debugMessage = ""; } // Are we exiting an inlined block? else { // Pop information about the enclosing line number. MyLineNumberBlock lineNumberBlock = enclosingLineNumbers.pop(); // Set this end of the block to the line at which it was // inlined. extendedLineNumberInfo.u2lineNumber = lineNumberBlock.enclosingLineNumber; extendedLineNumberInfo.source = lineNumberBlock.enclosingSource; // Reset the shift to the shift of the block. currentLineNumberShift = lineNumberBlock.lineNumberShift; debugMessage += String.format(" (exit to shift %s)", currentLineNumberShift); } } else { debugMessage += String.format(" (apply shift %s)", currentLineNumberShift); // Apply the shift. lineNumberInfo.u2lineNumber += currentLineNumberShift; } } previousLineNumberInfo = lineNumberInfo; debugMessage += String.format(" -> line %s", lineNumberInfo.u2lineNumber); logger.debug(debugMessage); } /** * This class represents a block of line numbers that originates from the * same inlined method. */ private static class MyLineNumberBlock { public final int lineNumberShift; public final int enclosingLineNumber; public final String enclosingSource; public MyLineNumberBlock(int lineNumberShift, int enclosingLineNumber, String enclosingSource) { this.lineNumberShift = lineNumberShift; this.enclosingLineNumber = enclosingLineNumber; this.enclosingSource = enclosingSource; } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/MemberPrivatizer.java ================================================ /* * 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.peephole; import proguard.classfile.*; import proguard.classfile.editor.MethodInvocationFixer; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.info.NonPrivateMemberMarker; /** * This MemberVisitor makes all class members that it visits private, unless * they have been marked by a NonPrivateMemberMarker. The invocations of * privatized methods still have to be fixed. * * @see NonPrivateMemberMarker * @see MethodInvocationFixer * @author Eric Lafortune */ public class MemberPrivatizer implements MemberVisitor { private final MemberVisitor extraMemberVisitor; /** * Creates a new MemberPrivatizer. */ public MemberPrivatizer() { this(null); } /** * Creates a new MemberPrivatizer. * @param extraMemberVisitor an optional extra visitor for all privatized * class members. */ public MemberPrivatizer(MemberVisitor extraMemberVisitor) { this.extraMemberVisitor = extraMemberVisitor; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Is the field unmarked? if (NonPrivateMemberMarker.canBeMadePrivate(programField)) { // Make the field private. programField.u2accessFlags = AccessUtil.replaceAccessFlags(programField.u2accessFlags, AccessConstants.PRIVATE); // Visit the field, if required. if (extraMemberVisitor != null) { extraMemberVisitor.visitProgramField(programClass, programField); } } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Is the method unmarked? if (NonPrivateMemberMarker.canBeMadePrivate(programMethod)) { // Make the method private and no longer final. programMethod.u2accessFlags = AccessUtil.replaceAccessFlags(programMethod.u2accessFlags, AccessConstants.PRIVATE); // Visit the method, if required. if (extraMemberVisitor != null) { extraMemberVisitor.visitProgramMethod(programClass, programMethod); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/MethodFinalizer.java ================================================ /* * 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.peephole; import proguard.classfile.*; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.KeepMarker; /** * This MemberVisitor makes the program methods that it visits * final, if possible. * * @author Eric Lafortune */ public class MethodFinalizer implements MemberVisitor { private final MemberVisitor extraMemberVisitor; private final MemberFinder memberFinder = new MemberFinder(); /** * Creates a new ClassFinalizer. */ public MethodFinalizer() { this(null); } /** * Creates a new ClassFinalizer. * @param extraMemberVisitor an optional extra visitor for all finalized * methods. */ public MethodFinalizer(MemberVisitor extraMemberVisitor) { this.extraMemberVisitor = extraMemberVisitor; } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { String name = programMethod.getName(programClass); // If the method is not already private/static/final/abstract, // and it is not a constructor, // and its class is final, // or it is not being kept and it is not overridden, // then make it final. if ((programMethod.u2accessFlags & (AccessConstants.PRIVATE | AccessConstants.STATIC | AccessConstants.FINAL | AccessConstants.ABSTRACT)) == 0 && !name.equals(ClassConstants.METHOD_NAME_INIT) && ((programClass.u2accessFlags & AccessConstants.FINAL) != 0 || (!KeepMarker.isKept(programMethod) && (programClass.subClassCount == 0 || !memberFinder.isOverriden(programClass, programMethod))))) { programMethod.u2accessFlags |= AccessConstants.FINAL; // Visit the method, if required. if (extraMemberVisitor != null) { extraMemberVisitor.visitProgramMethod(programClass, programMethod); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/MethodInliner.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.peephole; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.optimize.*; import proguard.optimize.info.*; import proguard.util.ProcessingFlagSetter; import proguard.util.ProcessingFlags; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Stack; /** * This AttributeVisitor is an abstract class representing a visitor considering to inline each method that it * visits in its usage sites. The behavior of whether a class is considered for inlining is controlled * by overriding the shouldInline method. * * There are some additional technical constraints imposed on whether the method is actually inlined * (see visitProgramMethod). * * @see SuperInvocationMarker * @see BackwardBranchMarker * @see AccessMethodMarker * @see SideEffectClassMarker * @author Eric Lafortune */ abstract public class MethodInliner implements AttributeVisitor, InstructionVisitor, ConstantVisitor, MemberVisitor, ExceptionInfoVisitor, LineNumberInfoVisitor { protected static final int MAXIMUM_INLINED_CODE_LENGTH_JVM = Integer.parseInt(System.getProperty("maximum.inlined.code.length", "8")); protected static final int MAXIMUM_INLINED_CODE_LENGTH_android = Integer.parseInt(System.getProperty("maximum.inlined.code.length", "32")); protected static final int MAXIMUM_RESULTING_CODE_LENGTH_JSE = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "7000")); protected static final int MAXIMUM_RESULTING_CODE_LENGTH_JME = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "2000")); protected static final int MAXIMUM_RESULTING_CODE_LENGTH_JVM = 65535; static final int METHOD_DUMMY_START_LINE_NUMBER = 0; static final int INLINED_METHOD_END_LINE_NUMBER = -1; private static final Logger logger = LogManager.getLogger(MethodInliner.class); protected final boolean microEdition; protected final boolean android; protected final int maxResultingCodeLength; protected final boolean allowAccessModification; protected final boolean usesOptimizationInfo; protected final InstructionVisitor extraInlinedInvocationVisitor; private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(); private final MemberVisitor accessMethodMarker = new OptimizationInfoMemberFilter( new AllAttributeVisitor( new AllInstructionVisitor( new MultiInstructionVisitor( new SuperInvocationMarker(), new AccessMethodMarker() )))); private final AttributeVisitor methodInvocationMarker = new AllInstructionVisitor( new MethodInvocationMarker()); private final StackSizeComputer stackSizeComputer = new StackSizeComputer(); private ProgramClass targetClass; private ProgramMethod targetMethod; private ConstantAdder constantAdder; private ExceptionInfoAdder exceptionInfoAdder; private int estimatedResultingCodeLength; private boolean inlining; private Stack inliningMethods = new Stack(); private boolean emptyInvokingStack; private boolean coveredByCatchAllHandler; private int exceptionInfoCount; private int uninitializedObjectCount; private int variableOffset; private boolean inlined; private boolean inlinedAny; private boolean copiedLineNumbers; private String source; private int minimumLineNumberIndex; /** * Creates a new MethodInliner. * * @param microEdition Indicates whether the resulting code is * targeted at Java Micro Edition. * @param android Indicates whether the resulting code is * targeted at the Dalvik VM. * @param allowAccessModification Indicates whether the access modifiers of * classes and class members can be changed * in order to inline methods. */ public MethodInliner(boolean microEdition, boolean android, boolean allowAccessModification) { this(microEdition, android, allowAccessModification, null); } /** * Creates a new MethodInliner. * * @param microEdition Indicates whether the resulting code is * targeted at Java Micro Edition. * @param android Indicates whether the resulting code is * targeted at the Dalvik VM. * @param allowAccessModification Indicates whether the access modifiers of * classes and class members can be changed * in order to inline methods. * @param extraInlinedInvocationVisitor An optional extra visitor for all * inlined invocation instructions. */ public MethodInliner(boolean microEdition, boolean android, boolean allowAccessModification, InstructionVisitor extraInlinedInvocationVisitor) { this(microEdition, android, defaultMaxResultingCodeLength(microEdition), allowAccessModification, true, extraInlinedInvocationVisitor); } /** * Creates a new MethodInliner. * * @param microEdition Indicates whether the resulting code is * targeted at Java Micro Edition. * @param android Indicates whether the resulting code is * targeted at the Dalvik VM. * @param maxResultingCodeLength Configures the inliner with a max resulting * code length. * @param allowAccessModification Indicates whether the access modifiers of * classes and class members can be changed * in order to inline methods. * @param usesOptimizationInfo Indicates whether this inliner needs to perform checks * that require optimization info. * @param extraInlinedInvocationVisitor An optional extra visitor for all * inlined invocation instructions. */ public MethodInliner(boolean microEdition, boolean android, int maxResultingCodeLength, boolean allowAccessModification, boolean usesOptimizationInfo, InstructionVisitor extraInlinedInvocationVisitor) { if (maxResultingCodeLength > MAXIMUM_RESULTING_CODE_LENGTH_JVM) { throw new IllegalArgumentException("Maximum resulting code length cannot exceed " + MAXIMUM_RESULTING_CODE_LENGTH_JVM); } this.microEdition = microEdition; this.android = android; this.maxResultingCodeLength = maxResultingCodeLength; this.allowAccessModification = allowAccessModification; this.usesOptimizationInfo = usesOptimizationInfo; this.extraInlinedInvocationVisitor = extraInlinedInvocationVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // TODO: Remove this when the method inliner has stabilized. // Catch any unexpected exceptions from the actual visiting method. try { // Process the code. visitCodeAttribute0(clazz, method, codeAttribute); } catch (RuntimeException ex) { logger.error("Unexpected error while inlining method:"); logger.error(" Target class = [{}]", targetClass.getName()); logger.error(" Target method = [{}{}]", targetMethod.getName(targetClass), targetMethod.getDescriptor(targetClass)); if (inlining) { logger.error(" Inlined class = [{}]", clazz.getName()); logger.error(" Inlined method = [{}{}]", method.getName(clazz), method.getDescriptor(clazz)); } logger.error(" Exception = [{}] ({})", ex.getClass().getName(), ex.getMessage(), ex); logger.error("Not inlining this method"); logger.debug("{}", () -> { StringWriter sw = new StringWriter(); targetMethod.accept(targetClass, new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); if (inlining) { logger.debug("{}", () -> { StringWriter sw = new StringWriter(); method.accept(clazz, new ClassPrinter(new PrintWriter(sw))); return sw.toString(); }); } if (logger.getLevel().isLessSpecificThan(Level.DEBUG)) { throw ex; } } } public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) { if (!inlining) { // codeAttributeComposer.DEBUG = DEBUG = // clazz.getName().equals("abc/Def") && // method.getName(clazz).equals("abc"); targetClass = (ProgramClass)clazz; targetMethod = (ProgramMethod)method; constantAdder = new ConstantAdder(targetClass); exceptionInfoAdder = new ExceptionInfoAdder(targetClass, codeAttributeComposer); estimatedResultingCodeLength = codeAttribute.u4codeLength; inliningMethods.clear(); uninitializedObjectCount = method.getName(clazz).equals(ClassConstants.METHOD_NAME_INIT) ? 1 : 0; inlinedAny = false; codeAttributeComposer.reset(); stackSizeComputer.visitCodeAttribute(clazz, method, codeAttribute); // Append the body of the code. copyCode(clazz, method, codeAttribute); // Update the code attribute if any code has been inlined. if (inlinedAny) { codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute); // Update the super/private/package/protected accessing flags. if (usesOptimizationInfo) { method.accept(clazz, accessMethodMarker); } } targetClass = null; targetMethod = null; constantAdder = null; } // Only inline the method if // 1. The shouldInline method returns true AND // 2. The resulting estimated code attribute length is below the specified limit else if (shouldInline(clazz, method, codeAttribute) && estimatedResultingCodeLength + codeAttribute.u4codeLength < maxResultingCodeLength) { logger.debug("MethodInliner: inlining [{}.{}{}] in [{}.{}{}]", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz), targetClass.getName(), targetMethod.getName(targetClass), targetMethod.getDescriptor(targetClass) ); // Ignore the removal of the original method invocation, // the addition of the parameter setup, and // the modification of a few inlined instructions. estimatedResultingCodeLength += codeAttribute.u4codeLength; // Append instructions to store the parameters. storeParameters(clazz, method); // Inline the body of the code. copyCode(clazz, method, codeAttribute); inlined = true; inlinedAny = true; } } public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) { // Remember the source if we're inlining a method. source = inlining ? clazz.getName() + '.' + method.getName(clazz) + method.getDescriptor(clazz) + ':' + lineNumberTableAttribute.getLowestLineNumber() + ':' + lineNumberTableAttribute.getHighestLineNumber() : null; // Insert all line numbers, possibly partly before previously inserted // line numbers. lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this); copiedLineNumbers = true; } /** * Appends instructions to pop the parameters for the given method, storing * them in new local variables. */ private void storeParameters(Clazz clazz, Method method) { String descriptor = method.getDescriptor(clazz); boolean isStatic = (method.getAccessFlags() & AccessConstants.STATIC) != 0; // Count the number of parameters, taking into account their categories. int parameterSize = ClassUtil.internalMethodParameterSize(descriptor); int parameterOffset = isStatic ? 0 : 1; // Store the parameter types. String[] parameterTypes = new String[parameterSize]; InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); for (int parameterIndex = 0; parameterIndex < parameterSize; parameterIndex++) { String parameterType = internalTypeEnumeration.nextType(); parameterTypes[parameterIndex] = parameterType; if (ClassUtil.internalTypeSize(parameterType) == 2) { parameterIndex++; } } codeAttributeComposer.beginCodeFragment(parameterSize+1); // Go over the parameter types backward, storing the stack entries // in their corresponding variables. for (int parameterIndex = parameterSize-1; parameterIndex >= 0; parameterIndex--) { String parameterType = parameterTypes[parameterIndex]; if (parameterType != null) { byte opcode; switch (parameterType.charAt(0)) { case TypeConstants.BOOLEAN: case TypeConstants.BYTE: case TypeConstants.CHAR: case TypeConstants.SHORT: case TypeConstants.INT: opcode = Instruction.OP_ISTORE; break; case TypeConstants.LONG: opcode = Instruction.OP_LSTORE; break; case TypeConstants.FLOAT: opcode = Instruction.OP_FSTORE; break; case TypeConstants.DOUBLE: opcode = Instruction.OP_DSTORE; break; default: opcode = Instruction.OP_ASTORE; break; } codeAttributeComposer.appendInstruction(parameterSize-parameterIndex-1, new VariableInstruction(opcode, variableOffset + parameterOffset + parameterIndex)); } } // Put the 'this' reference in variable 0 (plus offset). if (!isStatic) { codeAttributeComposer.appendInstruction(parameterSize, new VariableInstruction(Instruction.OP_ASTORE, variableOffset)); } codeAttributeComposer.endCodeFragment(); } /** * Appends the code of the given code attribute. */ private void copyCode(Clazz clazz, Method method, CodeAttribute codeAttribute) { // The code may expand, due to expanding constant and variable // instructions. codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); // Copy the instructions. codeAttribute.instructionsAccept(clazz, method, this); // Append a label just after the code. codeAttributeComposer.appendLabel(codeAttribute.u4codeLength); // Copy the exceptions. codeAttribute.exceptionsAccept(clazz, method, exceptionInfoAdder); // Copy the processing flags that need to be copied as well. targetMethod.accept(targetClass, new ProcessingFlagSetter(method.getProcessingFlags() & ProcessingFlags.COPYABLE_PROCESSING_FLAGS)); // Copy the line numbers. copiedLineNumbers = false; // The line numbers need to be inserted sequentially. minimumLineNumberIndex = 0; codeAttribute.attributesAccept(clazz, method, this); // Make sure we at least have some entry at the start of the method. if (!copiedLineNumbers) { String source = inlining ? clazz.getName() + '.' + method.getName(clazz) + method.getDescriptor(clazz) + ":0:0" : null; minimumLineNumberIndex = codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, new ExtendedLineNumberInfo(0, METHOD_DUMMY_START_LINE_NUMBER, source)) + 1; } // Add a marker at the end of an inlined method. // The marker will be corrected in LineNumberLinearizer, // so it points to the line of the enclosing method. if (inlining) { String source = clazz.getName() + '.' + method.getName(clazz) + method.getDescriptor(clazz) + ":0:0"; minimumLineNumberIndex = codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, new ExtendedLineNumberInfo(codeAttribute.u4codeLength, INLINED_METHOD_END_LINE_NUMBER, source)) + 1; } codeAttributeComposer.endCodeFragment(); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { codeAttributeComposer.appendInstruction(offset, instruction); } public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { // Are we inlining this instruction? if (inlining) { // Replace any return instructions by branches to the end of the code. switch (simpleInstruction.opcode) { case Instruction.OP_IRETURN: case Instruction.OP_LRETURN: case Instruction.OP_FRETURN: case Instruction.OP_DRETURN: case Instruction.OP_ARETURN: case Instruction.OP_RETURN: // Are we not at the last instruction? if (offset < codeAttribute.u4codeLength-1) { // Replace the return instruction by a branch instruction. Instruction branchInstruction = new BranchInstruction(Instruction.OP_GOTO_W, codeAttribute.u4codeLength - offset); codeAttributeComposer.appendInstruction(offset, branchInstruction); } else { // Just leave out the instruction, but put in a label, // for the sake of any other branch instructions. codeAttributeComposer.appendLabel(offset); } return; } } codeAttributeComposer.appendInstruction(offset, simpleInstruction); } public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { // Are we inlining this instruction? if (inlining) { // Update the variable index. variableInstruction.variableIndex += variableOffset; } codeAttributeComposer.appendInstruction(offset, variableInstruction); } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { // Is it a method invocation? switch (constantInstruction.opcode) { case Instruction.OP_NEW: uninitializedObjectCount++; break; case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_INVOKESTATIC: case Instruction.OP_INVOKEINTERFACE: // See if we can inline it. inlined = false; // Append a label, in case the invocation will be inlined. codeAttributeComposer.appendLabel(offset); emptyInvokingStack = !inlining && stackSizeComputer.isReachable(offset) && stackSizeComputer.getStackSizeAfter(offset) == 0; variableOffset += codeAttribute.u2maxLocals; // Check if the method invocation is covered by a catch-all // exception handler. coveredByCatchAllHandler = false; exceptionInfoCount = 0; codeAttribute.exceptionsAccept(clazz, method, offset, this); coveredByCatchAllHandler = exceptionInfoCount <= 0 || coveredByCatchAllHandler; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); variableOffset -= codeAttribute.u2maxLocals; // Was the method inlined? if (inlined) { if (extraInlinedInvocationVisitor != null) { extraInlinedInvocationVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); } // The invocation itself is no longer necessary. return; } break; } // Are we inlining this instruction? if (inlining) { // Make sure the constant is present in the constant pool of the // target class. constantInstruction.constantIndex = constantAdder.addConstant(clazz, constantInstruction.constantIndex); } codeAttributeComposer.appendInstruction(offset, constantInstruction); } // Implementations for ConstantVisitor. public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { anyMethodrefConstant.referencedMethodAccept(this); } // Implementations for MemberVisitor. public void visitAnyMember(Clazz Clazz, Member member) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { int accessFlags = programMethod.getAccessFlags(); logger.trace("MethodInliner: checking [{}.{}{}] in [{}.{}{}]", programClass.getName(), programMethod.getName(programClass), programMethod.getDescriptor(programClass), targetClass.getName(), targetMethod.getName(targetClass), targetMethod.getDescriptor(targetClass) ); if (DEBUG("Access?") && // Only inline the method if it is private, static, or final. // This currently precludes default interface methods, because // they can't be final. (accessFlags & (AccessConstants.PRIVATE | AccessConstants.STATIC | AccessConstants.FINAL)) != 0 && DEBUG("Synchronized?") && // Only inline the method if it is not synchronized, etc. (accessFlags & (AccessConstants.SYNCHRONIZED | AccessConstants.NATIVE | AccessConstants.ABSTRACT)) == 0 && DEBUG("Init?") && // Don't inline an method, except in an method in the // same class. // (!programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_INIT) || // (programClass.equals(targetClass) && // targetMethod.getName(targetClass).equals(ClassConstants.METHOD_NAME_INIT))) && !programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_INIT) && DEBUG("Self?") && // Don't inline a method into itself. (!programMethod.equals(targetMethod) || !programClass.equals(targetClass)) && DEBUG("Recurse?") && // Only inline the method if it isn't recursing. !inliningMethods.contains(programMethod) && DEBUG("Version?") && // Only inline the method if its target class has at least the // same version number as the source class, in order to avoid // introducing incompatible constructs. targetClass.u4version >= programClass.u4version && DEBUG("Super?") && // The below checks require optimization info to be set. (!usesOptimizationInfo || ( // Don't inline methods that must be preserved. !KeepMarker.isKept(programMethod) && // Only inline the method if it doesn't invoke a super method or a // dynamic method, or if it is in the same class. (!SuperInvocationMarker.invokesSuperMethods(programMethod) && !DynamicInvocationMarker.invokesDynamically(programMethod) || programClass.equals(targetClass)) && DEBUG("Branch?") && // Only inline the method if it doesn't branch backward while there // are uninitialized objects. (!BackwardBranchMarker.branchesBackward(programMethod) || uninitializedObjectCount == 0) && DEBUG("Access private?") && // Only inline if the code access of the inlined method allows it. (allowAccessModification || ((!AccessMethodMarker.accessesPrivateCode(programMethod) || programClass.equals(targetClass)) && (!AccessMethodMarker.accessesPackageCode(programMethod) || ClassUtil.internalPackageName(programClass.getName()).equals( ClassUtil.internalPackageName(targetClass.getName()))))) && DEBUG("Access private in subclass?") && // Only inline a method from a superclass if it doesn't access // private code (with invokespecial), because we can't fix the // invocation. (test2172) [DGD-1258] (!AccessMethodMarker.accessesPrivateCode(programMethod) || programClass.equals(targetClass) || !targetClass.extendsOrImplements(programClass)) && DEBUG("Access protected?") && // Only inline code that accesses protected code into the same // class. (!AccessMethodMarker.accessesProtectedCode(programMethod) || programClass.equals(targetClass)) && DEBUG("Synchronization?") && // if the method to be inlined has a synchronized block only inline it into // the target method if its invocation is covered by a catchall handler or // none at all. This might happen if the target method has been obfuscated // with fake exception handlers. (!SynchronizedBlockMethodMarker.hasSynchronizedBlock(programMethod) || coveredByCatchAllHandler) && DEBUG("Final fields?") && // Methods assigning final fields cannot be inlined, at least on Android // this leads to VerifyErrors at runtime. // This should normally not happen anyways, but some tools modify/generate // bytecode that would lead to such situations, e.g. jacoco, see DGD-561. !FinalFieldAssignmentMarker.assignsFinalField(programMethod) && DEBUG("Catch?") && // Only inline the method if it doesn't catch exceptions, or if it // is invoked with an empty stack. (!CatchExceptionMarker.catchesExceptions(programMethod) || emptyInvokingStack) && DEBUG("Stack?") && // Only inline the method if it always returns with an empty // stack. !NonEmptyStackReturnMarker.returnsWithNonEmptyStack(programMethod) && DEBUG("Side effects?") && // Only inline the method if its related static initializers don't // have any side effects. !SideEffectClassChecker.mayHaveSideEffects(targetClass, programClass, programMethod)))) { boolean oldInlining = inlining; inlining = true; inliningMethods.push(programMethod); // Inline the method body. programMethod.attributesAccept(programClass, this); if (usesOptimizationInfo) { // Update the optimization information of the target method. if (!KeepMarker.isKept(targetMethod)) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(targetMethod) .merge(MethodOptimizationInfo.getMethodOptimizationInfo(programMethod)); } // Increment the invocation count of referenced methods again, // since they are now invoked from the inlined code too. programMethod.attributesAccept(programClass, methodInvocationMarker); } inlining = oldInlining; inliningMethods.pop(); } else if (programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_INIT)) { uninitializedObjectCount--; } } @Override public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { if (libraryMethod.getName(libraryClass).equals(ClassConstants.METHOD_NAME_INIT)) { uninitializedObjectCount--; } } // Implementations for LineNumberInfoVisitor. public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo) { try { String newSource = lineNumberInfo.getSource() != null ? lineNumberInfo.getSource() : source; LineNumberInfo newLineNumberInfo = newSource != null ? new ExtendedLineNumberInfo(lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber, newSource) : new LineNumberInfo(lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber); minimumLineNumberIndex = codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, newLineNumberInfo) + 1; } catch (IllegalArgumentException e) { if (logger.getLevel().isLessSpecificThan(Level.DEBUG)) { logger.error("Invalid line number while inlining method:"); logger.error(" Target class = [{}]", targetClass.getName()); logger.error(" Target method = [{}{}]", targetMethod.getName(targetClass), targetMethod.getDescriptor(targetClass)); if (inlining) { logger.error(" Inlined class = [{}]", clazz.getName()); logger.error(" Inlined method = [{}{}]", method.getName(clazz), method.getDescriptor(clazz)); } logger.error(" Exception = [{}] ({})", e.getClass().getName(), e.getMessage()); } } } // Implementations for ExceptionInfoVisitor. public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) { exceptionInfoCount++; coveredByCatchAllHandler |= exceptionInfo.u2catchType == 0; } // Small helper methods. private static int defaultMaxResultingCodeLength(boolean microEdition) { return microEdition ? MAXIMUM_RESULTING_CODE_LENGTH_JME : MAXIMUM_RESULTING_CODE_LENGTH_JSE; } /** * Returns true, while printing out the given debug message. */ private boolean DEBUG(String string) { logger.trace(" {}", string); return true; } /** * Returns whether the method with the given descriptor returns an int-like (boolean, * byte, char or short) value. */ private boolean returnsIntLike(String methodDescriptor) { char returnChar = methodDescriptor.charAt(methodDescriptor.length() - 1); return returnChar == TypeConstants.BOOLEAN || returnChar == TypeConstants.BYTE || returnChar == TypeConstants.CHAR || returnChar == TypeConstants.SHORT; } /** * Indicates whether this method should be inlined. Subclasses can overwrite * this method to change which methods are inlined. * * Note that the method will always still first be tested on whether they can technically * be inlined (see `visitProgramMethod`) and only then will this method be called to decide * whether to actually inline or not. * * @param method The method that is eligible for inlining. */ abstract protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAttribute); } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/NoConstructorReferenceReplacer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.ClassPrinter; import proguard.optimize.info.*; import java.io.IOException; /** * This InstructionVisitor replaces instance references on classes without * constructors in all methods that it visits. * * @author Joachim Vandersmissen */ public class NoConstructorReferenceReplacer implements InstructionVisitor, ConstantVisitor { private final CodeAttributeEditor codeAttributeEditor; private final InstructionVisitor extraReferenceVisitor; private boolean containsConstructors; /** * Creates a new NoConstructorReferenceReplacer. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. */ public NoConstructorReferenceReplacer(CodeAttributeEditor codeAttributeEditor) { this(codeAttributeEditor, null); } /** * Creates a new GotoGotoReplacer. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. * @param extraReferenceVisitor an optional extra visitor for all replaced * instance references. */ public NoConstructorReferenceReplacer(CodeAttributeEditor codeAttributeEditor, InstructionVisitor extraReferenceVisitor) { this.codeAttributeEditor = codeAttributeEditor; this.extraReferenceVisitor = extraReferenceVisitor; } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { // Is it a method invocation? switch (constantInstruction.opcode) { case Instruction.OP_INVOKEVIRTUAL: case Instruction.OP_INVOKESPECIAL: case Instruction.OP_GETFIELD: case Instruction.OP_PUTFIELD: { // Does the referenced class not contain any constructors? clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); if (!containsConstructors) { // Replace with aconst_null + athrow. codeAttributeEditor.replaceInstruction(offset, new Instruction[] { new SimpleInstruction(Instruction.OP_ACONST_NULL), new SimpleInstruction(Instruction.OP_ATHROW) }); // Visit the instruction, if required. if (extraReferenceVisitor != null) { extraReferenceVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); } } } } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) { } public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { Clazz referencedClass = fieldrefConstant.referencedClass; containsConstructors = // We have to assume an unknown referenced class always has constructors. referencedClass == null || ContainsConstructorsMarker.containsConstructors(referencedClass); } public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { Clazz referencedClass = anyMethodrefConstant.referencedClass; containsConstructors = // We have to assume an unknown referenced class always has constructors. referencedClass == null || // Interfaces never have constructors but can still be the target of // invokevirtual and invokespecial instructions. We don't want to // replace those. (referencedClass.getAccessFlags() & AccessConstants.INTERFACE) != 0 || ContainsConstructorsMarker.containsConstructors(referencedClass); } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/NopRemover.java ================================================ /* * 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.peephole; import proguard.classfile.*; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This InstructionVisitor removes all nop instructions that it encounters. * * @author Eric Lafortune */ public class NopRemover implements InstructionVisitor { private final CodeAttributeEditor codeAttributeEditor; private final InstructionVisitor extraInstructionVisitor; /** * Creates a new NopRemover. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. */ public NopRemover(CodeAttributeEditor codeAttributeEditor) { this(codeAttributeEditor, null); } /** * Creates a new NopRemover. * @param codeAttributeEditor a code editor that can be used for * accumulating changes to the code. * @param extraInstructionVisitor an optional extra visitor for all removed * nop instructions. */ public NopRemover(CodeAttributeEditor codeAttributeEditor, InstructionVisitor extraInstructionVisitor) { this.codeAttributeEditor = codeAttributeEditor; this.extraInstructionVisitor = extraInstructionVisitor; } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { // Check if the instruction is a nop instruction. if (simpleInstruction.opcode == Instruction.OP_NOP && !codeAttributeEditor.isModified(offset)) { codeAttributeEditor.deleteInstruction(offset); // Visit the instruction, if required. if (extraInstructionVisitor != null) { extraInstructionVisitor.visitSimpleInstruction(clazz, method, codeAttribute, offset, simpleInstruction); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/ReachableCodeMarker.java ================================================ /* * 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.peephole; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.editor.ClassEstimates; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import java.util.Arrays; /** * This AttributeVisitor finds all instruction offsets, branch targets, and * exception targets in the CodeAttribute objects that it visits. * * @author Eric Lafortune */ public class ReachableCodeMarker implements AttributeVisitor, InstructionVisitor, ExceptionInfoVisitor { private boolean[] isReachable = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; private boolean next; private boolean evaluateExceptions; /** * Returns whether the instruction at the given offset is reachable in * the CodeAttribute that was visited most recently. */ public boolean isReachable(int offset) { return isReachable[offset]; } /** * Returns whether any of the instructions at the given offsets are * reachable in the CodeAttribute that was visited most recently. */ public boolean isReachable(int startOffset, int endOffset) { // Check if any of the instructions is reachable. for (int offset = startOffset; offset < endOffset; offset++) { if (isReachable[offset]) { return true; } } return false; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Make sure there is a sufficiently large array. int codeLength = codeAttribute.u4codeLength; if (isReachable.length < codeLength) { // Create a new array. isReachable = new boolean[codeLength]; } else { // Reset the array. Arrays.fill(isReachable, 0, codeLength, false); } // Mark the code, starting at the entry point. markCode(clazz, method, codeAttribute, 0); // Mark the exception handlers, iterating as long as necessary. do { evaluateExceptions = false; codeAttribute.exceptionsAccept(clazz, method, this); } while (evaluateExceptions); } // Implementations for InstructionVisitor. public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) { byte opcode = simpleInstruction.opcode; if (opcode == Instruction.OP_IRETURN || opcode == Instruction.OP_LRETURN || opcode == Instruction.OP_FRETURN || opcode == Instruction.OP_DRETURN || opcode == Instruction.OP_ARETURN || opcode == Instruction.OP_RETURN || opcode == Instruction.OP_ATHROW) { next = false; } } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { } public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { if (variableInstruction.opcode == Instruction.OP_RET) { next = false; } } public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) { // Mark the branch target. markBranchTarget(clazz, method, codeAttribute, offset + branchInstruction.branchOffset); byte opcode = branchInstruction.opcode; if (opcode == Instruction.OP_GOTO || opcode == Instruction.OP_GOTO_W) { next = false; } } public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction) { // Mark the branch targets of the default jump offset. markBranchTarget(clazz, method, codeAttribute, offset + switchInstruction.defaultOffset); // Mark the branch targets of the jump offsets. markBranchTargets(clazz, method, codeAttribute, offset, switchInstruction.jumpOffsets); next = false; } // Implementations for ExceptionInfoVisitor. public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) { // Mark the exception handler if it's relevant. if (!isReachable(exceptionInfo.u2handlerPC) && isReachable(exceptionInfo.u2startPC, exceptionInfo.u2endPC)) { markCode(clazz, method, codeAttribute, exceptionInfo.u2handlerPC); evaluateExceptions = true; } } // Small utility methods. /** * Marks the branch targets of the given jump offsets for the instruction * at the given offset. */ private void markBranchTargets(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int[] jumpOffsets) { for (int index = 0; index < jumpOffsets.length; index++) { markCode(clazz, method, codeAttribute, offset + jumpOffsets[index]); } } /** * Marks the branch target at the given offset. */ private void markBranchTarget(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset) { boolean oldNext = next; markCode(clazz, method, codeAttribute, offset); next = oldNext; } /** * Marks the code starting at the given offset. */ private void markCode(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset) { boolean oldNext = next; byte[] code = codeAttribute.code; // Continue with the current instruction as long as we haven't marked it // yet. while (!isReachable[offset]) { // Get the current instruction. Instruction instruction = InstructionFactory.create(code, offset); // Mark it as reachable. isReachable[offset] = true; // By default, we'll assume we can continue with the next // instruction in a moment. next = true; // Mark the branch targets, if any. instruction.accept(clazz, method, codeAttribute, offset, this); // Can we really continue with the next instruction? if (!next) { break; } // Go to the next instruction. offset += instruction.length(offset); } next = oldNext; } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/RetargetedClassFilter.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates its visits to one of two other given * ClassVisitor instances, depending on whether the classes are marked to be * retargeted or not. * * @see ClassMerger * * @author Eric Lafortune */ public class RetargetedClassFilter implements ClassVisitor { private final ClassVisitor retargetedClassVisitor; private final ClassVisitor otherClassVisitor; /** * Creates a new RetargetedClassFilter. * * @param retargetedClassVisitor the class visitor to which visits to * classes that are marked to be retargeted * will be delegated. */ public RetargetedClassFilter(ClassVisitor retargetedClassVisitor) { this(retargetedClassVisitor, null); } /** * Creates a new RetargetedClassFilter. * * @param retargetedClassVisitor the class visitor to which visits to * classes that are marked to be retargeted * will be delegated. * @param otherClassVisitor the class visitor to which visits to * classes that are not marked to be * retargeted will be delegated. */ public RetargetedClassFilter(ClassVisitor retargetedClassVisitor, ClassVisitor otherClassVisitor) { this.retargetedClassVisitor = retargetedClassVisitor; this.otherClassVisitor = otherClassVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { // Is the class marked to be retargeted? ClassVisitor classVisitor = ClassMerger.getTargetClass(programClass) != null ? retargetedClassVisitor : otherClassVisitor; if (classVisitor != null) { classVisitor.visitProgramClass(programClass); } } @Override public void visitLibraryClass(LibraryClass libraryClass) { // A library class can't be retargeted. if (otherClassVisitor != null) { otherClassVisitor.visitLibraryClass(libraryClass); } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/RetargetedInnerClassAttributeRemover.java ================================================ /* * 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.peephole; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.ClassVisitor; import java.util.Arrays; /** * This ClassVisitor removes InnerClasses and EnclosingMethod attributes in * classes that are retargeted or that refer to classes that are retargeted. * * @see ClassMerger * @author Eric Lafortune */ public class RetargetedInnerClassAttributeRemover implements ClassVisitor, AttributeVisitor, InnerClassesInfoVisitor, ConstantVisitor { private boolean retargeted; // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { int attributesCount = programClass.u2attributesCount; Attribute[] attributes = programClass.attributes; int newAtributesCount = 0; // Copy over all non-retargeted attributes. for (int index = 0; index < attributesCount; index++) { Attribute attribute = attributes[index]; // Check if it's an InnerClasses or EnclosingMethod attribute in // a retargeted class or referring to a retargeted class. retargeted = false; attribute.accept(programClass, this); if (!retargeted) { attributes[newAtributesCount++] = attribute; } } // Clean up any remaining array elements. Arrays.fill(attributes, newAtributesCount, attributesCount, null); // Update the number of attributes. programClass.u2attributesCount = newAtributesCount; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { // Check whether the class itself is retargeted. checkTarget(clazz); if (!retargeted) { // Check whether the referenced classes are retargeted. innerClassesAttribute.innerClassEntriesAccept(clazz, this); int classesCount = innerClassesAttribute.u2classesCount; InnerClassesInfo[] classes = innerClassesAttribute.classes; int newClassesCount = 0; // Copy over all non-retargeted attributes. for (int index = 0; index < classesCount; index++) { InnerClassesInfo classInfo = classes[index]; // Check if the outer class or inner class is a retargeted class. retargeted = false; classInfo.outerClassConstantAccept(clazz, this); classInfo.innerClassConstantAccept(clazz, this); if (!retargeted) { classes[newClassesCount++] = classInfo; } } // Clean up any remaining array elements. Arrays.fill(classes, newClassesCount, classesCount, null); // Update the number of classes. innerClassesAttribute.u2classesCount = newClassesCount; // Remove the attribute altogether if it's empty. retargeted = newClassesCount == 0; } } public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) { // Check whether the class itself is retargeted. checkTarget(clazz); // Check whether the referenced class is retargeted. checkTarget(enclosingMethodAttribute.referencedClass); } // Implementations for InnerClassesInfoVisitor. public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { // Check whether the inner class or the outer class are retargeted. innerClassesInfo.innerClassConstantAccept(clazz, this); innerClassesInfo.outerClassConstantAccept(clazz, this); } // Implementations for ConstantVisitor. public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Check whether the referenced class is retargeted. checkTarget(classConstant.referencedClass); } // Small utility methods. /** * Sets the global return value to true if the given class is retargeted. */ private void checkTarget(Clazz clazz) { if (clazz != null && ClassMerger.getTargetClass(clazz) != null) { retargeted = true; } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/ShortMethodInliner.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.peephole; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This AttributeVisitor inlines short methods in the code attributes that it visits. */ public class ShortMethodInliner extends MethodInliner { public ShortMethodInliner(boolean microEdition, boolean android, boolean allowAccessModification) { super(microEdition, android, allowAccessModification); } public ShortMethodInliner(boolean microEdition, boolean android, boolean allowAccessModification, InstructionVisitor extraInlinedInvocationVisitor) { super(microEdition, android, allowAccessModification, extraInlinedInvocationVisitor); } // Implementations for MethodInliner. @Override protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAttribute) { return codeAttribute.u4codeLength <= (android ? MAXIMUM_INLINED_CODE_LENGTH_android : MAXIMUM_INLINED_CODE_LENGTH_JVM); } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/SingleInvocationMethodInliner.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.peephole; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.optimize.info.MethodInvocationMarker; /** * This AttributeVisitor inlines methods that are only invoked once, in the code attributes that it visits. */ public class SingleInvocationMethodInliner extends MethodInliner { public SingleInvocationMethodInliner(boolean microEdition, boolean android, boolean allowAccessModification) { super(microEdition, android, allowAccessModification); } public SingleInvocationMethodInliner(boolean microEdition, boolean android, boolean allowAccessModification, InstructionVisitor extraInlinedInvocationVisitor) { super(microEdition, android, allowAccessModification, extraInlinedInvocationVisitor); } // Implementations for MethodInliner. @Override protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAttribute) { return MethodInvocationMarker.getInvocationCount(method) == 1; } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/TargetClassChanger.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.visitor.*; import proguard.optimize.info.ClassOptimizationInfo; import proguard.util.ArrayUtil; import java.util.Arrays; /** * This ClassVisitor replaces references to classes and class members if the * classes have targets that are intended to replace them. * * @see VerticalClassMerger * @see ClassReferenceFixer * @see MemberReferenceFixer * @author Eric Lafortune */ public class TargetClassChanger implements ClassVisitor, // Implementation interfaces. ConstantVisitor, MemberVisitor, AttributeVisitor, RecordComponentInfoVisitor, LocalVariableInfoVisitor, LocalVariableTypeInfoVisitor, AnnotationVisitor, ElementValueVisitor { private static final Logger logger = LogManager.getLogger(TargetClassChanger.class); // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { // We're only making changes locally in the class. // Not all other classes may have been retargeted yet. // Change the references of the constant pool. programClass.constantPoolEntriesAccept(this); // Change the references of the class members. programClass.fieldsAccept(this); programClass.methodsAccept(this); // Change the references of the attributes. programClass.attributesAccept(this); // Remove duplicate interfaces and interface classes that have ended // up pointing to the class itself. boolean[] delete = null; for (int index = 0; index < programClass.u2interfacesCount; index++) { Clazz interfaceClass = programClass.getInterface(index); if (interfaceClass != null && (programClass.equals(interfaceClass) || containsInterfaceClass(programClass, index, interfaceClass))) { // Lazily create the array. if (delete == null) { delete = new boolean[programClass.u2interfacesCount]; } delete[index] = true; } } if (delete != null) { new InterfaceDeleter(delete, false).visitProgramClass(programClass); } // Is the class being retargeted? Clazz targetClass = ClassMerger.getTargetClass(programClass); if (targetClass != null) { // We're not changing anything special in the superclass and // interface hierarchy of the retargeted class. The shrinking // step will remove the class for us. // Restore the class name. We have to add a new class entry // to avoid an existing entry with the same name being reused. The // names have to be fixed later, based on their referenced classes. programClass.u2thisClass = addNewClassConstant(programClass, programClass.getName(), programClass); // This class will no longer have any subclasses, because their // subclasses and interfaces will be retargeted. programClass.subClassCount = 0; // Clear all elements. Arrays.fill(programClass.subClasses, null); } else { // Retarget subclasses, avoiding duplicates and the class itself. programClass.subClassCount = updateUniqueReferencedClasses(programClass.subClasses, programClass.subClassCount, programClass); // TODO: Maybe restore private method references. } // The class merger already made sure that any new interfaces got this // class as a subclass. Now the superclass and interfaces have been // retargeted, also make sure that they have this class as a subclass. // Retargeting subclasses may not be sufficient, for example if a class // didn't have subclasses before. ConstantVisitor subclassAdder = new ReferencedClassVisitor( new SubclassFilter(programClass, new SubclassAdder(programClass))); programClass.superClassConstantAccept(subclassAdder); programClass.interfaceConstantsAccept(subclassAdder); } @Override public void visitLibraryClass(LibraryClass libraryClass) { // Retarget subclasses, avoiding duplicates. libraryClass.subClassCount = updateUniqueReferencedClasses(libraryClass.subClasses, libraryClass.subClassCount, libraryClass); } // Implementations for MemberVisitor. @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Change the referenced class. programField.referencedClass = updateReferencedClass(programField.referencedClass); // Change the references of the attributes. programField.attributesAccept(programClass, this); } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Change the referenced classes. updateReferencedClasses(programMethod.referencedClasses); // Change the references of the attributes. programMethod.attributesAccept(programClass, this); } // Implementations for ConstantVisitor. @Override public void visitAnyConstant(Clazz clazz, Constant constant) {} @Override public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Does the string refer to a class, due to a Class.forName construct? Clazz referencedClass = stringConstant.referencedClass; Clazz newReferencedClass = updateReferencedClass(referencedClass); if (referencedClass != newReferencedClass) { // Change the referenced class. stringConstant.referencedClass = newReferencedClass; // Change the referenced class member, if applicable. stringConstant.referencedMember = updateReferencedMember(stringConstant.referencedMember, stringConstant.getString(clazz), null, newReferencedClass); } } @Override public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { // Change the referenced classes. updateReferencedClasses(invokeDynamicConstant.referencedClasses); } @Override public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant refConstant) { Clazz referencedClass = refConstant.referencedClass; Clazz newReferencedClass = updateReferencedClass(referencedClass); if (referencedClass != newReferencedClass) { logger.debug("TargetClassChanger:"); logger.debug(" [{}] changing reference from [{}.{}{}]", clazz.getName(), refConstant.referencedClass.getName(), refConstant.referencedMethod.getName(refConstant.referencedClass), refConstant.referencedMethod.getDescriptor(refConstant.referencedClass) ); // Change the referenced class. refConstant.referencedClass = newReferencedClass; // Change the referenced class member. refConstant.referencedMethod = (Method)updateReferencedMember(refConstant.referencedMethod, refConstant.getName(clazz), refConstant.getType(clazz), newReferencedClass); logger.debug(" [{}] to [{}.{}]", clazz.getName(), refConstant.referencedClass, refConstant.referencedMethod ); } } @Override public void visitFieldrefConstant(Clazz clazz, FieldrefConstant refConstant) { Clazz referencedClass = refConstant.referencedClass; Clazz newReferencedClass = updateReferencedClass(referencedClass); if (referencedClass != newReferencedClass) { logger.debug("TargetClassChanger:"); logger.debug(" [{}] changing reference from [{}.{}{}]", clazz.getName(), refConstant.referencedClass.getName(), refConstant.referencedField.getName(refConstant.referencedClass), refConstant.referencedField.getDescriptor(refConstant.referencedClass) ); // Change the referenced class. refConstant.referencedClass = newReferencedClass; // Change the referenced class member. refConstant.referencedField = (Field)updateReferencedMember(refConstant.referencedField, refConstant.getName(clazz), refConstant.getType(clazz), newReferencedClass); logger.debug(" [{}] to [{}.{}]", clazz.getName(), refConstant.referencedClass, refConstant.referencedField ); } } @Override public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Change the referenced class. classConstant.referencedClass = updateReferencedClass(classConstant.referencedClass); } @Override public void visitMethodTypeConstant(Clazz clazz, MethodTypeConstant methodTypeConstant) { updateReferencedClasses(methodTypeConstant.referencedClasses); } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Change the references of the attributes. codeAttribute.attributesAccept(clazz, method, this); } @Override public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { // Change the references of the local variables. localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } @Override public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { // Change the references of the local variables. localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } @Override public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) { try { // Change the referenced classes. updateReferencedClasses(signatureAttribute.referencedClasses); } catch (RuntimeException e) { logger.error("Unexpected error while adapting signatures for merged classes:"); logger.error(" Class = [{}]", clazz.getName()); logger.error(" Signature = [{}]", signatureAttribute.getSignature(clazz)); Clazz[] referencedClasses = signatureAttribute.referencedClasses; if (referencedClasses != null) { for (int index = 0; index < referencedClasses.length; index++) { Clazz referencedClass = referencedClasses[index]; logger.error(" Referenced class #{} = {}", index, referencedClass); if (referencedClass != null) { ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(referencedClass); logger.error(" info = {}", info); if (info != null) { logger.error(" target = {}", info.getTargetClass()); } } } } logger.error(" Exception = [{}] ({})", e.getClass().getName(), e.getMessage()); throw e; } } @Override public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) { // Change the references of the annotations. annotationsAttribute.annotationsAccept(clazz, this); } @Override public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) { // Change the references of the annotations. parameterAnnotationsAttribute.annotationsAccept(clazz, method, this); } @Override public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) { // Change the references of the annotation. annotationDefaultAttribute.defaultValueAccept(clazz, this); } // Implementations for RecordComponentInfoVisitor. public void visitRecordComponentInfo(Clazz clazz, RecordComponentInfo recordComponentInfo) { // Don't change the referenced field; it's still the original one // in this class. // Change the references of the attributes. recordComponentInfo.attributesAccept(clazz, this); } // Implementations for LocalVariableInfoVisitor. @Override public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) { // Change the referenced class. localVariableInfo.referencedClass = updateReferencedClass(localVariableInfo.referencedClass); } // Implementations for LocalVariableTypeInfoVisitor. @Override public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) { // Change the referenced classes. updateReferencedClasses(localVariableTypeInfo.referencedClasses); } // Implementations for AnnotationVisitor. @Override public void visitAnnotation(Clazz clazz, Annotation annotation) { // Change the referenced classes. updateReferencedClasses(annotation.referencedClasses); // Change the references of the element values. annotation.elementValuesAccept(clazz, this); } // Implementations for ElementValueVisitor. @Override public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue) { Clazz referencedClass = elementValue.referencedClass; Clazz newReferencedClass = updateReferencedClass(referencedClass); if (referencedClass != newReferencedClass) { // Change the referenced annotation class. elementValue.referencedClass = newReferencedClass; // Change the referenced method. elementValue.referencedMethod = (Method)updateReferencedMember(elementValue.referencedMethod, elementValue.getMethodName(clazz), null, newReferencedClass); } } @Override public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) { // Change the referenced annotation class and method. visitAnyElementValue(clazz, annotation, constantElementValue); } @Override public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) { // Change the referenced annotation class and method. visitAnyElementValue(clazz, annotation, enumConstantElementValue); // Change the referenced classes. updateReferencedClasses(enumConstantElementValue.referencedClasses); } @Override public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) { // Change the referenced annotation class and method. visitAnyElementValue(clazz, annotation, classElementValue); // Change the referenced classes. updateReferencedClasses(classElementValue.referencedClasses); } @Override public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) { // Change the referenced annotation class and method. visitAnyElementValue(clazz, annotation, annotationElementValue); // Change the references of the annotation. annotationElementValue.annotationAccept(clazz, this); } @Override public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) { // Change the referenced annotation class and method. visitAnyElementValue(clazz, annotation, arrayElementValue); // Change the references of the element values. arrayElementValue.elementValuesAccept(clazz, annotation, this); } // Small utility methods. /** * Returns whether the given class contains the given interface * class in its first given number of interfaces. */ private boolean containsInterfaceClass(Clazz clazz, int interfaceCount, Clazz interfaceClass) { for (int index = 0; index < interfaceCount; index++) { if (interfaceClass.equals(clazz.getInterface(index))) { return true; } } return false; } /** * Updates the retargeted classes in the given array of unique classes. * Optionally gets a class to avoid in the results. */ private int updateUniqueReferencedClasses(Clazz[] referencedClasses, int referencedClassCount, Clazz avoidClass) { int newIndex = 0; for (int index = 0; index < referencedClassCount; index++) { Clazz referencedClass = referencedClasses[index]; // Isn't the subclass being retargeted? Clazz targetClass = ClassMerger.getTargetClass(referencedClasses[index]); if (targetClass == null) { // Keep the original class. referencedClasses[newIndex++] = referencedClass; } // Isn't the targeted class present yet? else if (!targetClass.equals(avoidClass) && ArrayUtil.indexOf(referencedClasses, referencedClassCount, targetClass) < 0) { // Replace the original class by its targeted class. referencedClasses[newIndex++] = targetClass; } } // Clear the remaining elements. Arrays.fill(referencedClasses, newIndex, referencedClassCount, null); return newIndex; } /** * Updates the retargeted classes in the given array of classes. */ private void updateReferencedClasses(Clazz[] referencedClasses) { if (referencedClasses == null) { return; } for (int index = 0; index < referencedClasses.length; index++) { referencedClasses[index] = updateReferencedClass(referencedClasses[index]); } } /** * Returns the retargeted class of the given class. */ private Clazz updateReferencedClass(Clazz referencedClass) { if (referencedClass == null) { return null; } Clazz targetClazz = ClassMerger.getTargetClass(referencedClass); return targetClazz != null ? targetClazz : referencedClass; } /** * Returns the retargeted class member of the given class member. */ private Member updateReferencedMember(Member referencedMember, String name, String type, Clazz newReferencedClass) { if (referencedMember == null) { return null; } return referencedMember instanceof Field ? newReferencedClass.findField(name, type) : newReferencedClass.findMethod(name, type); } /** * Explicitly adds a new class constant for the given class in the given * program class. */ private int addNewClassConstant(ProgramClass programClass, String className, Clazz referencedClass) { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass); int nameIndex = constantPoolEditor.addUtf8Constant(className); int classConstantIndex = constantPoolEditor.addConstant(new ClassConstant(nameIndex, referencedClass)); return classConstantIndex; } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/UnreachableCodeRemover.java ================================================ /* * 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.peephole; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; /** * This InstructionVisitor deletes blocks of code that can never be reached by * regular calls or branches. * * @author Eric Lafortune */ public class UnreachableCodeRemover implements AttributeVisitor, InstructionVisitor { private static final Logger logger = LogManager.getLogger(UnreachableCodeRemover.class); private final InstructionVisitor extraInstructionVisitor; private final ReachableCodeMarker reachableCodeMarker = new ReachableCodeMarker(); private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); /** * Creates a new UnreachableCodeRemover. */ public UnreachableCodeRemover() { this(null); } /** * Creates a new UnreachableCodeRemover. * @param extraInstructionVisitor an optional extra visitor for all * deleted instructions. */ public UnreachableCodeRemover(InstructionVisitor extraInstructionVisitor) { this.extraInstructionVisitor = extraInstructionVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // DEBUG = // clazz.getName().equals("abc/Def") && // method.getName(clazz).equals("abc"); // TODO: Remove this when the code has stabilized. // Catch any unexpected exceptions from the actual visiting method. try { // Process the code. visitCodeAttribute0(clazz, method, codeAttribute); } catch (RuntimeException ex) { logger.error("Unexpected error while removing unreachable code:"); logger.error(" Class = [{}]", clazz.getName()); logger.error(" Method = [{}{}]", method.getName(clazz), method.getDescriptor(clazz)); logger.error(" Exception = [{}] ({})", ex.getClass().getName(), ex.getMessage()); throw ex; } } public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) { logger.debug("UnreachableCodeRemover: {}.{}{}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz) ); reachableCodeMarker.visitCodeAttribute(clazz, method, codeAttribute); codeAttributeEditor.reset(codeAttribute.u4codeLength); codeAttribute.instructionsAccept(clazz, method, this); codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { logger.debug(" {} {}", reachableCodeMarker.isReachable(offset) ? "+" : "-", instruction.toString(offset) ); // Is this instruction unreachable? if (!reachableCodeMarker.isReachable(offset)) { // Then delete it. codeAttributeEditor.deleteInstruction(offset); // Visit the instruction, if required. if (extraInstructionVisitor != null) { instruction.accept(clazz, method, codeAttribute, offset, extraInstructionVisitor); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/UnreachableExceptionRemover.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.instruction.*; /** * This AttributeVisitor removes exception handlers that are unreachable in the * code attributes that it visits. * * @author Eric Lafortune */ public class UnreachableExceptionRemover implements AttributeVisitor, ExceptionInfoVisitor { private final ExceptionInfoVisitor extraExceptionInfoVisitor; /** * Creates a new UnreachableExceptionRemover. */ public UnreachableExceptionRemover() { this(null); } /** * Creates a new UnreachableExceptionRemover. * @param extraExceptionInfoVisitor an optional extra visitor for all * removed exceptions. */ public UnreachableExceptionRemover(ExceptionInfoVisitor extraExceptionInfoVisitor) { this.extraExceptionInfoVisitor = extraExceptionInfoVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Go over the exception table. codeAttribute.exceptionsAccept(clazz, method, this); // Remove exceptions with empty code blocks. codeAttribute.u2exceptionTableLength = removeEmptyExceptions(codeAttribute.exceptionTable, codeAttribute.u2exceptionTableLength); } // Implementations for ExceptionInfoVisitor. public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) { if (!mayThrowExceptions(clazz, method, codeAttribute, exceptionInfo.u2startPC, exceptionInfo.u2endPC)) { // Make the code block empty. exceptionInfo.u2endPC = exceptionInfo.u2startPC; if (extraExceptionInfoVisitor != null) { extraExceptionInfoVisitor.visitExceptionInfo(clazz, method, codeAttribute, exceptionInfo); } } } // Small utility methods. /** * Returns whether the specified block of code may throw exceptions. */ private boolean mayThrowExceptions(Clazz clazz, Method method, CodeAttribute codeAttribute, int startOffset, int endOffset) { byte[] code = codeAttribute.code; // Go over all instructions. int offset = startOffset; while (offset < endOffset) { // Get the current instruction. Instruction instruction = InstructionFactory.create(code, offset); // Check if it may be throwing exceptions. if (instruction.mayInstanceThrowExceptions(clazz)) { return true; } // Go to the next instruction. offset += instruction.length(offset); } return false; } /** * Returns the given list of exceptions, without the ones that have empty * code blocks. */ private int removeEmptyExceptions(ExceptionInfo[] exceptionInfos, int exceptionInfoCount) { // Overwrite all empty exceptions. int newIndex = 0; for (int index = 0; index < exceptionInfoCount; index++) { ExceptionInfo exceptionInfo = exceptionInfos[index]; if (exceptionInfo.u2startPC < exceptionInfo.u2endPC) { exceptionInfos[newIndex++] = exceptionInfo; } } return newIndex; } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/VariableShrinker.java ================================================ /* * 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.peephole; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.VariableEditor; import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; import proguard.optimize.*; import proguard.optimize.info.*; /** * This MemberVisitor removes unused local variables from the code of the methods * that it visits. * * @see ParameterUsageMarker * @see MethodStaticizer * @see MethodDescriptorShrinker * @author Eric Lafortune */ public class VariableShrinker implements AttributeVisitor { private static final Logger logger = LogManager.getLogger(VariableShrinker.class); private final MemberVisitor extraVariableMemberVisitor; private final VariableUsageMarker variableUsageMarker = new VariableUsageMarker(); private final VariableEditor variableEditor = new VariableEditor(); /** * Creates a new VariableShrinker. */ public VariableShrinker() { this(null); } /** * Creates a new VariableShrinker with an extra visitor. * @param extraVariableMemberVisitor an optional extra visitor for all * removed variables. */ public VariableShrinker(MemberVisitor extraVariableMemberVisitor) { this.extraVariableMemberVisitor = extraVariableMemberVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { if ((method.getAccessFlags() & AccessConstants.ABSTRACT) == 0) { // Compute the parameter size. int parameterSize = ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz), method.getAccessFlags()); // Get the total size of the local variable frame. int maxLocals = codeAttribute.u2maxLocals; logger.debug("VariableShrinker: {}.{}{}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz)); logger.debug(" Parameter size = {}", parameterSize); logger.debug(" Max locals = {}", maxLocals); // Figure out the local variables that are used by the code. variableUsageMarker.visitCodeAttribute(clazz, method, codeAttribute); // Delete unused local variables from the local variable frame. variableEditor.reset(maxLocals); for (int variableIndex = parameterSize; variableIndex < maxLocals; variableIndex++) { // Is the variable not required? if (!variableUsageMarker.isVariableUsed(variableIndex)) { logger.debug(" Deleting local variable #{}", variableIndex); // Delete the unused variable. variableEditor.deleteVariable(variableIndex); // Visit the method, if required. if (extraVariableMemberVisitor != null) { method.accept(clazz, extraVariableMemberVisitor); } } } // Shift all remaining parameters and variables in the byte code. variableEditor.visitCodeAttribute(clazz, method, codeAttribute); } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/VerticalClassMerger.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import proguard.classfile.*; import proguard.classfile.visitor.*; /** * This ClassVisitor inlines the direct subclasses into the program classes * that it visits, whenever possible. * * @see ClassMerger * @author Eric Lafortune */ public class VerticalClassMerger implements ClassVisitor { private final boolean allowAccessModification; private final boolean mergeInterfacesAggressively; private final ClassVisitor extraClassVisitor; /** * Creates a new VerticalClassMerger. * * @param allowAccessModification specifies whether the access modifiers * of classes can be changed in order to * merge them. * @param mergeInterfacesAggressively specifies whether interfaces may * be merged aggressively. */ public VerticalClassMerger(boolean allowAccessModification, boolean mergeInterfacesAggressively) { this(allowAccessModification, mergeInterfacesAggressively, null); } /** * Creates a new VerticalClassMerger. * @param allowAccessModification specifies whether the access modifiers * of classes can be changed in order to * merge them. * @param mergeInterfacesAggressively specifies whether interfaces may * be merged aggressively. * @param extraClassVisitor an optional extra visitor for all * merged classes. */ public VerticalClassMerger(boolean allowAccessModification, boolean mergeInterfacesAggressively, ClassVisitor extraClassVisitor ) { this.allowAccessModification = allowAccessModification; this.mergeInterfacesAggressively = mergeInterfacesAggressively; this.extraClassVisitor = extraClassVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Try inlining all immediate subclasses into this class. programClass.subclassesAccept(new InjectedClassFilter(null, new ClassMerger(programClass, allowAccessModification, mergeInterfacesAggressively, false, extraClassVisitor))); } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/WrapperClassMerger.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import proguard.classfile.*; import proguard.classfile.visitor.*; import proguard.optimize.info.WrapperClassMarker; /** * This ClassVisitor inlines the wrapper classes that it visits into their * wrapped classes, whenever possible. * * A wrapper class can be a simple (anonymous) inner class that implements * some interface, for example. * * @see WrapperClassMarker * @see ClassMerger * @author Eric Lafortune */ public class WrapperClassMerger implements ClassVisitor { private final boolean allowAccessModification; private final ClassVisitor extraClassVisitor; /** * Creates a new WrappedClassMerger. * * @param allowAccessModification specifies whether the access modifiers * of classes can be changed in order to * merge them. */ public WrapperClassMerger(boolean allowAccessModification) { this(allowAccessModification, null); } /** * Creates a new WrappedClassMerger. * @param allowAccessModification specifies whether the access modifiers * of classes can be changed in order to * merge them. * @param extraClassVisitor an optional extra visitor for all * merged classes. */ public WrapperClassMerger(boolean allowAccessModification, ClassVisitor extraClassVisitor) { this.allowAccessModification = allowAccessModification; this.extraClassVisitor = extraClassVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Only merge wrapper classes that extend java.lang.Object. if (ClassConstants.NAME_JAVA_LANG_OBJECT.equals(programClass.getSuperName())) { // Do we have a wrapped program class? Clazz wrappedClass = WrapperClassMarker.getWrappedClass(programClass); if (wrappedClass instanceof ProgramClass) { // Try inlining the wrapper class into the wrapped class. new ClassMerger((ProgramClass)wrappedClass, allowAccessModification, false, true, extraClassVisitor).visitProgramClass(programClass); } } } } ================================================ FILE: base/src/main/java/proguard/optimize/peephole/WrapperClassUseSimplifier.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.peephole; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.ClassVisitor; import proguard.optimize.info.WrapperClassMarker; /** * This AttributeVisitor simplifies the use of retargeted wrapper classes in * the code attributes that it visits. More specifically, it replaces * "new Wrapper(targetClass)" by "targetClass", and * "wrapper.field" by "wrapper". * You still need to retarget all class references, replacing references to * the wrapper class by references to the target class. * * @see WrapperClassMarker * @see ClassMerger * @see RetargetedClassFilter * @see TargetClassChanger * @author Eric Lafortune */ public class WrapperClassUseSimplifier implements AttributeVisitor, InstructionVisitor, ConstantVisitor, ClassVisitor { private static final Logger logger = LogManager.getLogger(WrapperClassUseSimplifier.class); private final InstructionVisitor extraInstructionVisitor; private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true); // Fields acting as parameters and return values for the visitor methods. private Clazz wrappedClass; private boolean isDupedOnStack; private Instruction storeInstruction; /** * Creates a new WrapperClassUseSimplifier. */ public WrapperClassUseSimplifier() { this(null); } /** * Creates a new WrapperClassUseSimplifier. * @param extraInstructionVisitor an optional extra visitor for all * simplified instructions. */ public WrapperClassUseSimplifier(InstructionVisitor extraInstructionVisitor) { this.extraInstructionVisitor = extraInstructionVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { logger.debug("WrapperClassUseSimplifier: {}.{}{}", clazz.getName(), method.getName(clazz), method.getDescriptor(clazz)); int codeLength = codeAttribute.u4codeLength; // Reset the code changes. codeAttributeEditor.reset(codeLength); // Edit the instructions. codeAttribute.instructionsAccept(clazz, method, this); // Apply all accumulated changes to the code. codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { switch (constantInstruction.opcode) { case Instruction.OP_NEW: { // Is it instantiating a wrapper class? if (isReferencingWrapperClass(clazz, constantInstruction.constantIndex)) { // We at least need to handle any of these sequences: // new -> (nothing) // new / dup -> (nothing) // new / astore / aload -> (nothing) // new / dup / astore -> (nothing) // Replace the new instance by a dummy null. codeAttributeEditor.deleteInstruction(offset); // Look at the next instruction. offset += constantInstruction.length(offset); Instruction nextInstruction = InstructionFactory.create(codeAttribute.code, offset); // Is the next instruction a (typical) dup instruction? isDupedOnStack = nextInstruction.opcode == Instruction.OP_DUP; if (isDupedOnStack) { // Delete the duplicate. codeAttributeEditor.deleteInstruction(offset); // Look at the next instruction. offset += nextInstruction.length(offset); nextInstruction = InstructionFactory.create(codeAttribute.code, offset); } // Is the next instruction a (less common) store instruction? if (nextInstruction.canonicalOpcode() == Instruction.OP_ASTORE) { // Delete the store. codeAttributeEditor.deleteInstruction(offset); // Remember the store instruction, so we can use it to // store the wrapped class later on. storeInstruction = nextInstruction; // Look at the next instruction. offset += nextInstruction.length(offset); nextInstruction = InstructionFactory.create(codeAttribute.code, offset); // Is the next instruction the corresponding load // instruction? if (nextInstruction.canonicalOpcode() == Instruction.OP_ALOAD && ((VariableInstruction)storeInstruction).variableIndex == ((VariableInstruction)nextInstruction ).variableIndex) { // Delete the load. codeAttributeEditor.deleteInstruction(offset); // Remember that the original code had a duplicate // of the wrapper on the stack. isDupedOnStack = true; } } else { storeInstruction = null; } if (extraInstructionVisitor != null) { extraInstructionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); } } break; } case Instruction.OP_INVOKESPECIAL: { // Is it initializing a wrapper class? if (isReferencingWrapperClass(clazz, constantInstruction.constantIndex)) { // Replace the initializer invocation, popping or storing // the wrapped instance from the stack. // TODO: May still fail with nested initializers for different wrapper classes. // Do we have a store instruction? if (storeInstruction != null) { // The wrapper was stored beforehand. Store the wrapped // instance now. // wrapper.(target) -> astore codeAttributeEditor.replaceInstruction(offset, storeInstruction); } else if (isDupedOnStack) { // The wrapper was originally duplicated on the stack. // Delete the initializer invocation, leaving just the // wrapped instance on the stack: // wrapper.(target) -> (target) // A subsequent astore/putfield/... should consume it. codeAttributeEditor.deleteInstruction(offset); } else { // The wrapper wasn't duplicated on the stack or // stored in a variable. Just pop the wrapped instance. // wrapper.(target) -> pop codeAttributeEditor.replaceInstruction(offset, new SimpleInstruction(Instruction.OP_POP)); } } break; } case Instruction.OP_GETFIELD: { // Is it retrieving the field of the wrapper class? if (isReferencingWrapperClass(clazz, constantInstruction.constantIndex)) { // Delete the retrieval: // wrapper.field -> wrapper codeAttributeEditor.deleteInstruction(offset); } break; } } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { // Is the constant retrieving from a wrapper class? fieldrefConstant.referencedClassAccept(this); } public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { if (methodrefConstant.getName(clazz).equals(ClassConstants.METHOD_NAME_INIT)) { // Is the constant referring to a wrapper class? methodrefConstant.referencedClassAccept(this); } } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Is the constant referring to a wrapper class? classConstant.referencedClassAccept(this); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { wrappedClass = ClassMerger.getTargetClass(programClass); } // Small utility methods. /** * Returns whether the constant at the given offset is referencing a * wrapper class (different from the given class) that is being retargeted. */ private boolean isReferencingWrapperClass(Clazz clazz, int constantIndex) { wrappedClass = null; clazz.constantPoolEntryAccept(constantIndex, this); return wrappedClass != null; } } ================================================ FILE: base/src/main/java/proguard/pass/Pass.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.pass; import proguard.AppView; /** * The interface each ProGuard pass should implement. */ public interface Pass { /** * Returns the name of the pass. */ default String getName() { return getClass().getName(); } /** * Executes the pass. */ void execute(AppView appView) throws Exception; } ================================================ FILE: base/src/main/java/proguard/pass/PassRunner.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard.pass; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.*; import proguard.util.Benchmark; import proguard.util.TimeUtil; public class PassRunner { private static final Logger logger = LogManager.getLogger(PassRunner.class); private final Benchmark benchmark = new Benchmark(); public void run(Pass pass, AppView appView) throws Exception { benchmark.start(); pass.execute(appView); benchmark.stop(); logger.debug("Pass {} completed in {}", pass::getName, () -> TimeUtil.millisecondsToMinSecReadable(benchmark.getElapsedTimeMs())); } } ================================================ FILE: base/src/main/java/proguard/preverify/PreverificationClearer.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.preverify; import proguard.AppView; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.editor.NamedAttributeDeleter; import proguard.classfile.visitor.*; import proguard.pass.Pass; /** * This pass clears any JSE preverification information from the program classes. */ public class PreverificationClearer implements Pass { @Override public void execute(AppView appView) { appView.programClassPool.classesAccept( new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_6, new AllMethodVisitor( new AllAttributeVisitor( new NamedAttributeDeleter(Attribute.STACK_MAP_TABLE))))); } } ================================================ FILE: base/src/main/java/proguard/preverify/Preverifier.java ================================================ /* * 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.preverify; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.Configuration; import proguard.classfile.*; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.visitor.*; import proguard.pass.Pass; /** * This pass can preverify methods in program class pools, according to a given * configuration. * * @author Eric Lafortune */ public class Preverifier implements Pass { private static final Logger logger = LogManager.getLogger(Preverifier.class); private final Configuration configuration; public Preverifier(Configuration configuration) { this.configuration = configuration; } /** * Performs preverification of the given program class pool. */ @Override public void execute(AppView appView) { logger.info("Preverifying..."); // Clean up any old processing info. appView.programClassPool.classesAccept(new ClassCleaner()); // Preverify all methods. // Classes for JME must be preverified. // Classes for JSE 6 may optionally be preverified. // Classes for JSE 7 or higher must be preverified. appView.programClassPool.classesAccept( new ClassVersionFilter(configuration.microEdition ? VersionConstants.CLASS_VERSION_1_0 : VersionConstants.CLASS_VERSION_1_6, new AllMethodVisitor( new AllAttributeVisitor( new CodePreverifier(configuration.microEdition))))); } } ================================================ FILE: base/src/main/java/proguard/preverify/SubroutineInliner.java ================================================ /* * 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.preverify; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.Configuration; import proguard.classfile.*; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.visitor.*; import proguard.pass.Pass; /** * This pass can inline subroutines in methods. This is generally useful (i.e. * required) for preverifying code. * * @author Eric Lafortune */ public class SubroutineInliner implements Pass { private static final Logger logger = LogManager.getLogger(SubroutineInliner.class); private final Configuration configuration; public SubroutineInliner(Configuration configuration) { this.configuration = configuration; } /** * Performs subroutine inlining of the given program class pool. */ @Override public void execute(AppView appView) { logger.info("Inlining subroutines..."); // Clean up any old processing info. appView.programClassPool.classesAccept(new ClassCleaner()); // Inline all subroutines. ClassVisitor inliner = new AllMethodVisitor( new AllAttributeVisitor( new CodeSubroutineInliner())); // In Java Standard Edition, only class files from Java 6 or higher // should be preverified. if (!configuration.microEdition && !configuration.android) { inliner = new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_6, inliner); } appView.programClassPool.classesAccept(inliner); } } ================================================ FILE: base/src/main/java/proguard/shrink/AnnotationUsageMarker.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.visitor.*; import proguard.util.ProcessingFlags; /** * This AttributeVisitor recursively marks all necessary annotation information * in the attributes that it visits. * * @see ClassUsageMarker * * @author Eric Lafortune */ public class AnnotationUsageMarker implements AttributeVisitor, AnnotationVisitor, ElementValueVisitor, ConstantVisitor, ClassVisitor, MemberVisitor { private final ClassUsageMarker classUsageMarker; // Fields acting as a return parameters for several methods. private boolean attributeUsed; private boolean annotationUsed; private boolean allClassesUsed; private boolean methodUsed; /** * Creates a new AnnotationUsageMarker. * @param classUsageMarker the marker to mark and check the classes and * class members. */ public AnnotationUsageMarker(ClassUsageMarker classUsageMarker) { this.classUsageMarker = classUsageMarker; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) { // Mark the necessary annotation information. attributeUsed = false; annotationsAttribute.annotationsAccept(clazz, this); if (attributeUsed) { // We got a positive used flag, so some annotation is being used. // Mark this attribute as being used as well. classUsageMarker.markAsUsed(annotationsAttribute); markConstant(clazz, annotationsAttribute.u2attributeNameIndex); } } public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) { // Mark the necessary annotation information. attributeUsed = false; parameterAnnotationsAttribute.annotationsAccept(clazz, method, this); if (attributeUsed) { // We got a positive used flag, so some annotation is being used. // Mark this attribute as being used as well. classUsageMarker.markAsUsed(parameterAnnotationsAttribute); markConstant(clazz, parameterAnnotationsAttribute.u2attributeNameIndex); } } public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) { // Mark the necessary annotation information in any annotation elements. annotationDefaultAttribute.defaultValueAccept(clazz, this); // Always mark annotation defaults. classUsageMarker.markAsUsed(annotationDefaultAttribute); markConstant(clazz, annotationDefaultAttribute.u2attributeNameIndex); } // Implementations for AnnotationVisitor. public void visitAnnotation(Clazz clazz, Annotation annotation) { if (isReferencedClassUsed(annotation)) { // Mark the annotation as being used. classUsageMarker.markAsUsed(annotation); markConstant(clazz, annotation.u2typeIndex); // Mark the necessary element values. annotation.elementValuesAccept(clazz, this); // The return values. annotationUsed = true; attributeUsed = true; } } // Implementations for ElementValueVisitor. public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) { if (isReferencedMethodUsed(constantElementValue)) { // Mark the element value as being used. classUsageMarker.markAsUsed(constantElementValue); markConstant(clazz, constantElementValue.u2elementNameIndex); markConstant(clazz, constantElementValue.u2constantValueIndex); } } public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) { if (isReferencedMethodUsed(enumConstantElementValue)) { // Check the referenced classes. allClassesUsed = true; enumConstantElementValue.referencedClassesAccept(this); if (allClassesUsed) { // Mark the element value as being used. classUsageMarker.markAsUsed(enumConstantElementValue); markConstant(clazz, enumConstantElementValue.u2elementNameIndex); markConstant(clazz, enumConstantElementValue.u2typeNameIndex); markConstant(clazz, enumConstantElementValue.u2constantNameIndex); } } } public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) { if (isReferencedMethodUsed(classElementValue)) { // Mark the element value as being used. classUsageMarker.markAsUsed(classElementValue); markConstant(clazz, classElementValue.u2elementNameIndex); markConstant(clazz, classElementValue.u2classInfoIndex); // Mark the referenced classes, since they can be retrieved from // the annotation and then used. // TODO: This could mark more annotation methods, affecting other annotations. classElementValue.referencedClassesAccept(classUsageMarker); } } public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) { if (isReferencedMethodUsed(annotationElementValue)) { boolean oldAnnotationUsed = annotationUsed; // Check and mark the contained annotation. annotationUsed = false; annotationElementValue.annotationAccept(clazz, this); if (annotationUsed) { // Mark the element value as being used. classUsageMarker.markAsUsed(annotationElementValue); markConstant(clazz, annotationElementValue.u2elementNameIndex); } annotationUsed = oldAnnotationUsed; } } public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) { if (isReferencedMethodUsed(arrayElementValue)) { // Check and mark the contained element values. arrayElementValue.elementValuesAccept(clazz, annotation, this); // Mark the element value as being used. classUsageMarker.markAsUsed(arrayElementValue); markConstant(clazz, arrayElementValue.u2elementNameIndex); } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) { // Mark the constant (and apply the optional extra visitor). constant.accept(clazz, classUsageMarker); } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Is the class constant marked as being used? if (!classUsageMarker.isUsed(classConstant)) { // Check the referenced class. allClassesUsed = true; classConstant.referencedClassAccept(this); // Is the referenced class marked as being used? if (allClassesUsed) { // Mark the class constant and its Utf8 constant. visitAnyConstant(clazz, classConstant); } } } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { allClassesUsed &= classUsageMarker.isUsed(programClass); // Add this special case for kotlin DebugMetadata annotation. // Unless it is explicitly kept for shrinking, remove it as it exposes // information about the original class / member names, see DGD-1361. if (programClass.getName().equals(KotlinConstants.NAME_KOTLIN_COROUTINES_DEBUG_METADATA)) { allClassesUsed &= (programClass.getProcessingFlags() & ProcessingFlags.DONT_SHRINK) != 0; } } @Override public void visitLibraryClass(LibraryClass libraryClass) { // Add this special case for kotlin Metadata / DebugMetadata annotation // in library projects. We want to strip it (default is to keep -libraryjar // annotations). You can override this by keeping kotlin.Metadata in configuration. if (libraryClass.getName().equals(KotlinConstants.NAME_KOTLIN_METADATA) || libraryClass.getName().equals(KotlinConstants.NAME_KOTLIN_COROUTINES_DEBUG_METADATA)) { allClassesUsed &= (libraryClass.getProcessingFlags() & ProcessingFlags.DONT_SHRINK) != 0; } } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { methodUsed = classUsageMarker.isUsed(programMethod); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { } // Small utility methods. /** * Returns whether the annotation class has been marked as being used. */ private boolean isReferencedClassUsed(Annotation annotation) { // Check if the referenced class is being used. allClassesUsed = true; annotation.referencedClassAccept(this); return allClassesUsed; } /** * Returns whether the annotation method has been marked as being used. */ private boolean isReferencedMethodUsed(ElementValue elementValue) { // Check if the referenced method is being used. methodUsed = true; elementValue.referencedMethodAccept(this); return methodUsed; } /** * Marks the specified constant pool entry. */ private void markConstant(Clazz clazz, int index) { if (index > 0) { clazz.constantPoolEntryAccept(index, this); } } } ================================================ FILE: base/src/main/java/proguard/shrink/ClassShrinker.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.util.*; import java.util.*; /** * This ClassVisitor removes constant pool entries, class members, and other * class elements that are not marked as being used. * * @see ClassUsageMarker * * @author Eric Lafortune */ public class ClassShrinker implements ClassVisitor, MemberVisitor, AttributeVisitor, RecordComponentInfoVisitor, AnnotationVisitor, ElementValueVisitor { private final SimpleUsageMarker usageMarker; private int[] constantIndexMap = new int[ClassEstimates.TYPICAL_CONSTANT_POOL_SIZE]; private int[] bootstrapMethodIndexMap = new int[ClassEstimates.TYPICAL_CONSTANT_POOL_SIZE]; private final MyAttributeShrinker attributeShrinker = new MyAttributeShrinker(); private final ConstantPoolRemapper constantPoolRemapper = new ConstantPoolRemapper(); private final BootstrapMethodRemapper bootstrapMethodRemapper = new BootstrapMethodRemapper(); private final MySignatureCleaner signatureCleaner = new MySignatureCleaner(); /** * Creates a new ClassShrinker. * @param usageMarker the usage marker that is used to mark the classes * and class members. */ public ClassShrinker(SimpleUsageMarker usageMarker) { this.usageMarker = usageMarker; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { // Mark the classes with processing flags if fields or methods are removed // (used for configuration debugging). programClass.fieldsAccept( new UsedMemberFilter(usageMarker, null, new MemberAccessFilter(AccessConstants.PUBLIC, 0, new MemberToClassVisitor( new MultiClassVisitor( new ProcessingFlagSetter(ProcessingFlags.REMOVED_FIELDS), new ProcessingFlagSetter(ProcessingFlags.REMOVED_PUBLIC_FIELDS) )), new MemberToClassVisitor( new ProcessingFlagSetter(ProcessingFlags.REMOVED_FIELDS))))); programClass.methodsAccept( new UsedMemberFilter(usageMarker, null, new ConstructorMethodFilter( new MemberAccessFilter(AccessConstants.PUBLIC, 0, new MemberToClassVisitor( new MultiClassVisitor( new ProcessingFlagSetter(ProcessingFlags.REMOVED_CONSTRUCTORS), new ProcessingFlagSetter(ProcessingFlags.REMOVED_PUBLIC_CONSTRUCTORS) )), new MemberToClassVisitor( new ProcessingFlagSetter(ProcessingFlags.REMOVED_CONSTRUCTORS))), new MemberAccessFilter(AccessConstants.PUBLIC, 0, new MemberToClassVisitor( new MultiClassVisitor( new ProcessingFlagSetter(ProcessingFlags.REMOVED_METHODS), new ProcessingFlagSetter(ProcessingFlags.REMOVED_PUBLIC_METHODS) )), new MemberToClassVisitor( new ProcessingFlagSetter(ProcessingFlags.REMOVED_METHODS)))))); // Shrink the arrays for constant pool, interfaces, fields, methods, // and class attributes. if (programClass.u2interfacesCount > 0) { new InterfaceDeleter(shrinkInterfaceFlags(programClass), true) .visitProgramClass(programClass); } // Shrink the arrays for nest members and permitted subclasses. programClass.attributesAccept(attributeShrinker); // Shrink the constant pool, also setting up an index map. int newConstantPoolCount = shrinkConstantPool(programClass.constantPool, programClass.u2constantPoolCount); int oldFieldsCount = programClass.u2fieldsCount; programClass.u2fieldsCount = shrinkArray(programClass.fields, programClass.u2fieldsCount); if (programClass.u2fieldsCount < oldFieldsCount) { programClass.processingFlags |= ProcessingFlags.REMOVED_FIELDS; } int oldMethodsCount = programClass.u2methodsCount; programClass.u2methodsCount = shrinkArray(programClass.methods, programClass.u2methodsCount); if (programClass.u2methodsCount < oldMethodsCount) { programClass.processingFlags |= ProcessingFlags.REMOVED_METHODS; } programClass.u2attributesCount = shrinkArray(programClass.attributes, programClass.u2attributesCount); // Compact the remaining fields, methods, and attributes, // and remap their references to the constant pool. programClass.fieldsAccept(this); programClass.methodsAccept(this); programClass.attributesAccept(this); // Remap the references to the constant pool if it has shrunk. if (newConstantPoolCount < programClass.u2constantPoolCount) { programClass.u2constantPoolCount = newConstantPoolCount; // Remap all constant pool references. constantPoolRemapper.setConstantIndexMap(constantIndexMap); constantPoolRemapper.visitProgramClass(programClass); } // Replace any unused classes in the signatures. programClass.fieldsAccept(new AllAttributeVisitor(signatureCleaner)); programClass.methodsAccept(new AllAttributeVisitor(signatureCleaner)); programClass.attributesAccept(signatureCleaner); // Compact the extra field pointing to the subclasses of this class. programClass.subClassCount = shrinkArray(programClass.subClasses, programClass.subClassCount); } @Override public void visitLibraryClass(LibraryClass libraryClass) { // Library classes are left unchanged. // Compact the extra field pointing to the subclasses of this class. libraryClass.subClassCount = shrinkArray(libraryClass.subClasses, libraryClass.subClassCount); } // Implementations for MemberVisitor. public void visitProgramMember(ProgramClass programClass, ProgramMember programMember) { // Shrink the attributes array. programMember.u2attributesCount = shrinkArray(programMember.attributes, programMember.u2attributesCount); // Shrink any attributes. programMember.attributesAccept(programClass, this); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { // Shrink the array of BootstrapMethodInfo objects. int newBootstrapMethodsCount = shrinkBootstrapMethodArray(bootstrapMethodsAttribute.bootstrapMethods, bootstrapMethodsAttribute.u2bootstrapMethodsCount); if (newBootstrapMethodsCount < bootstrapMethodsAttribute.u2bootstrapMethodsCount) { bootstrapMethodsAttribute.u2bootstrapMethodsCount = newBootstrapMethodsCount; // Remap all bootstrap method references. bootstrapMethodRemapper.setBootstrapMethodIndexMap(bootstrapMethodIndexMap); clazz.constantPoolEntriesAccept(bootstrapMethodRemapper); } } public void visitRecordAttribute(Clazz clazz, RecordAttribute recordAttribute) { // Shrink the array of RecordComponentInfo objects. recordAttribute.u2componentsCount = shrinkArray(recordAttribute.components, recordAttribute.u2componentsCount); // Shrink the attributes of the remaining components. recordAttribute.componentsAccept(clazz, this); } public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { // Shrink the array of InnerClassesInfo objects. innerClassesAttribute.u2classesCount = shrinkArray(innerClassesAttribute.classes, innerClassesAttribute.u2classesCount); } public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) { // Sometimes, a class is still referenced (apparently as a dummy class), // but its enclosing method is not. Then remove the reference to // the enclosing method. // E.g. the anonymous inner class javax.swing.JList$1 is defined inside // a constructor of javax.swing.JList, but it is also referenced as a // dummy argument in a constructor of javax.swing.JList$ListSelectionHandler. if (enclosingMethodAttribute.referencedMethod != null && !usageMarker.isUsed(enclosingMethodAttribute.referencedMethod)) { enclosingMethodAttribute.u2nameAndTypeIndex = 0; enclosingMethodAttribute.referencedMethod = null; } } public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Shrink the attributes array. codeAttribute.u2attributesCount = shrinkArray(codeAttribute.attributes, codeAttribute.u2attributesCount); // Shrink the attributes themselves. codeAttribute.attributesAccept(clazz, method, this); } public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { // Shrink the local variable info array. localVariableTableAttribute.u2localVariableTableLength = shrinkArray(localVariableTableAttribute.localVariableTable, localVariableTableAttribute.u2localVariableTableLength); } public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { // Shrink the local variable type info array. localVariableTypeTableAttribute.u2localVariableTypeTableLength = shrinkArray(localVariableTypeTableAttribute.localVariableTypeTable, localVariableTypeTableAttribute.u2localVariableTypeTableLength); } public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) { // Shrink the annotations array. annotationsAttribute.u2annotationsCount = shrinkArray(annotationsAttribute.annotations, annotationsAttribute.u2annotationsCount); // Shrink the annotations themselves. annotationsAttribute.annotationsAccept(clazz, this); } public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) { // Loop over all parameters. for (int parameterIndex = 0; parameterIndex < parameterAnnotationsAttribute.u1parametersCount; parameterIndex++) { // Shrink the parameter annotations array. parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex] = shrinkArray(parameterAnnotationsAttribute.parameterAnnotations[parameterIndex], parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex]); } // Shrink the annotations themselves. parameterAnnotationsAttribute.annotationsAccept(clazz, method, this); } // Implementations for RecordComponentInfoVisitor. public void visitRecordComponentInfo(Clazz clazz, RecordComponentInfo recordComponentInfo) { // Shrink the attributes array. recordComponentInfo.u2attributesCount = shrinkArray(recordComponentInfo.attributes, recordComponentInfo.u2attributesCount); // Shrink the remaining attributes. recordComponentInfo.attributesAccept(clazz, this); } // Implementations for AnnotationVisitor. public void visitAnnotation(Clazz clazz, Annotation annotation) { // Shrink the element values array. annotation.u2elementValuesCount = shrinkArray(annotation.elementValues, annotation.u2elementValuesCount); // Shrink the element values themselves. annotation.elementValuesAccept(clazz, this); } /** * This AttributeVisitor shrinks the nest members in the nest member * attributes and the permitted subclasses in the permitted subclasses * attributes that it visits. */ private class MyAttributeShrinker implements AttributeVisitor { public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitNestMembersAttribute(Clazz clazz, NestMembersAttribute nestMembersAttribute) { // Shrink the array of nest member indices. // We must do this before the corresponding constants are remapped. nestMembersAttribute.u2classesCount = shrinkConstantIndexArray(((ProgramClass)clazz).constantPool, nestMembersAttribute.u2classes, nestMembersAttribute.u2classesCount); } public void visitPermittedSubclassesAttribute(Clazz clazz, PermittedSubclassesAttribute permittedSubclassesAttribute) { // Shrink the array of nest member indices. // We must do this before the corresponding constants are remapped. permittedSubclassesAttribute.u2classesCount = shrinkConstantIndexArray(((ProgramClass)clazz).constantPool, permittedSubclassesAttribute.u2classes, permittedSubclassesAttribute.u2classesCount); } } /** * This AttributeVisitor updates the Utf8 constants of signatures * of classes, fields, and methods. */ private class MySignatureCleaner implements AttributeVisitor { public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) { Clazz[] referencedClasses = signatureAttribute.referencedClasses; if (referencedClasses != null) { // Go over the classes in the signature. String signature = signatureAttribute.getSignature(clazz); DescriptorClassEnumeration classEnumeration = new DescriptorClassEnumeration(signature); int referencedClassIndex = 0; // Start construction a new signature. StringBuffer newSignatureBuffer = new StringBuffer(); newSignatureBuffer.append(classEnumeration.nextFluff()); while (classEnumeration.hasMoreClassNames()) { String className = classEnumeration.nextClassName(); // Replace the class name if it is unused. Clazz referencedClass = referencedClasses[referencedClassIndex]; if (referencedClass != null && !usageMarker.isUsed(referencedClass)) { className = ClassConstants.NAME_JAVA_LANG_OBJECT; referencedClasses[referencedClassIndex] = null; } // Use a short name if it's an inner class after a '.' // separator. else if (classEnumeration.isInnerClassName()) { className = className.substring(className.lastIndexOf(TypeConstants.INNER_CLASS_SEPARATOR)+1); } referencedClassIndex++; newSignatureBuffer.append(className); newSignatureBuffer.append(classEnumeration.nextFluff()); } // Update the signature. ((Utf8Constant)((ProgramClass)clazz).constantPool[signatureAttribute.u2signatureIndex]).setString(newSignatureBuffer.toString()); } } } // Implementations for ElementValueVisitor. public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue) {} public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) { // Shrink the contained annotation. annotationElementValue.annotationAccept(clazz, this); } public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) { // Shrink the element values array. arrayElementValue.u2elementValuesCount = shrinkArray(arrayElementValue.elementValues, arrayElementValue.u2elementValuesCount); // Shrink the element values themselves. arrayElementValue.elementValuesAccept(clazz, annotation, this); } // Small utility methods. /** * Removes all entries that are not marked as being used from the given * constant pool. Creates a map from the old indices to the new indices * as a side effect. * @return the new number of entries. */ private int shrinkConstantPool(Constant[] constantPool, int length) { if (constantIndexMap.length < length) { constantIndexMap = new int[length]; } int counter = 1; boolean isUsed = false; // Shift the used constant pool entries together. for (int index = 1; index < length; index++) { constantIndexMap[index] = counter; Constant constant = constantPool[index]; // Is the constant being used? Don't update the flag if this is the // second half of a long entry. if (constant != null) { isUsed = usageMarker.isUsed(constant); } if (isUsed) { // Remember the new index. constantIndexMap[index] = counter; // Shift the constant pool entry. constantPool[counter++] = constant; } else { // Remember an invalid index. constantIndexMap[index] = -1; } } // Clear the remaining constant pool elements. Arrays.fill(constantPool, counter, length, null); return counter; } /** * Creates an array marking unused constant pool entries for all the * elements in the given array of constant pool indices. * @return an array of flags indicating unused elements. */ private boolean[] shrinkFlags(Constant[] constantPool, int[] array, int length) { boolean[] unused = new boolean[length]; // Remember the unused constants. for (int index = 0; index < length; index++) { if (!usageMarker.isUsed(constantPool[array[index]])) { unused[index] = true; } } return unused; } /** * Creates an array marking unused constant pool entries for all the * elements in the given array of constant pool indices, pointing to * class constants of interfaces. * @return an array of flags indicating unused elements. */ private boolean[] shrinkInterfaceFlags(ProgramClass programClass) { Constant[] constantPool = programClass.constantPool; int[] interfaces = programClass.u2interfaces; int interfacesCount = programClass.u2interfacesCount; // Collect the names of all indirectly implemented interfaces, unless // they are kept or the class itself is kept. That avoids problems if // some code applies reflection to the list of interfaces. Set indirectlyImplementedInterfaces = new HashSet(); if ((programClass.getProcessingFlags() & ProcessingFlags.DONT_SHRINK) == 0) { ConstantVisitor interfaceNameCollector = new ReferencedClassVisitor( new UsedClassFilter(usageMarker, new ClassHierarchyTraveler(false, true, true, false, new ProgramClassFilter( new UsedClassFilter(usageMarker, new ClassAccessFilter(AccessConstants.INTERFACE, 0, new ClassProcessingFlagFilter(0, ProcessingFlags.DONT_SHRINK, new ClassNameCollector(indirectlyImplementedInterfaces)))))))); programClass.superClassConstantAccept(interfaceNameCollector); programClass.interfaceConstantsAccept(interfaceNameCollector); } boolean[] unused = new boolean[interfacesCount]; // Remember the unused or unnecessary constants. for (int index = 0; index < interfacesCount; index++) { // The interface may be unused, or it may be unnecessary in the // list of implemented interfaces, if a superclass/interface // already implements it. ClassConstant interfaceClassConstant = (ClassConstant)constantPool[interfaces[index]]; String interfaceClassName = interfaceClassConstant.getName(programClass); if (!usageMarker.isUsed(interfaceClassConstant) || indirectlyImplementedInterfaces.contains(interfaceClassName)) { unused[index] = true; } } return unused; } /** * Removes all indices that point to unused constant pool entries * from the given array. * @return the new number of indices. */ private int shrinkConstantIndexArray(Constant[] constantPool, int[] array, int length) { int counter = 0; // Shift the used objects together. for (int index = 0; index < length; index++) { if (usageMarker.isUsed(constantPool[array[index]])) { array[counter++] = array[index]; } } // Clear the remaining array elements. Arrays.fill(array, counter, length, 0); return counter; } /** * Removes all entries that are not marked as being used from the given * array of bootstrap methods. Creates a map from the old indices to the * new indices as a side effect. * @return the new number of entries. */ private int shrinkBootstrapMethodArray(BootstrapMethodInfo[] bootstrapMethods, int length) { if (bootstrapMethodIndexMap.length < length) { bootstrapMethodIndexMap = new int[length]; } int counter = 0; // Shift the used bootstrap methods together. for (int index = 0; index < length; index++) { BootstrapMethodInfo bootstrapMethod = bootstrapMethods[index]; // Is the entry being used? if (usageMarker.isUsed(bootstrapMethod)) { // Remember the new index. bootstrapMethodIndexMap[index] = counter; // Shift the entry. bootstrapMethods[counter++] = bootstrapMethod; } else { // Remember an invalid index. bootstrapMethodIndexMap[index] = -1; } } // Clear the remaining bootstrap methods. Arrays.fill(bootstrapMethods, counter, length, null); return counter; } /** * Removes all Processable objects that are not marked as being used * from the given array. * @return the new number of Processable objects. */ private int shrinkArray(Processable[] array, int length) { int counter = 0; // Shift the used objects together. for (int index = 0; index < length; index++) { Processable processable = array[index]; if (usageMarker.isUsed(processable)) { array[counter++] = processable; } } // Clear any remaining array elements. if (counter < length) { Arrays.fill(array, counter, length, null); } return counter; } } ================================================ FILE: base/src/main/java/proguard/shrink/ClassUsageMarker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.shrink; import proguard.classfile.AccessConstants; import proguard.classfile.ClassConstants; import proguard.classfile.Clazz; import proguard.classfile.Field; import proguard.classfile.LibraryClass; import proguard.classfile.LibraryField; import proguard.classfile.LibraryMethod; import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramField; import proguard.classfile.ProgramMember; import proguard.classfile.ProgramMethod; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.BootstrapMethodInfo; import proguard.classfile.attribute.BootstrapMethodsAttribute; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.ConstantValueAttribute; import proguard.classfile.attribute.DeprecatedAttribute; import proguard.classfile.attribute.EnclosingMethodAttribute; import proguard.classfile.attribute.ExceptionInfo; import proguard.classfile.attribute.ExceptionsAttribute; import proguard.classfile.attribute.InnerClassesAttribute; import proguard.classfile.attribute.InnerClassesInfo; import proguard.classfile.attribute.LineNumberTableAttribute; import proguard.classfile.attribute.LocalVariableTableAttribute; import proguard.classfile.attribute.LocalVariableTypeTableAttribute; import proguard.classfile.attribute.MethodParametersAttribute; import proguard.classfile.attribute.NestHostAttribute; import proguard.classfile.attribute.NestMembersAttribute; import proguard.classfile.attribute.ParameterInfo; import proguard.classfile.attribute.PermittedSubclassesAttribute; import proguard.classfile.attribute.RecordAttribute; import proguard.classfile.attribute.SignatureAttribute; import proguard.classfile.attribute.SourceDebugExtensionAttribute; import proguard.classfile.attribute.SourceDirAttribute; import proguard.classfile.attribute.SourceFileAttribute; import proguard.classfile.attribute.SyntheticAttribute; import proguard.classfile.attribute.UnknownAttribute; import proguard.classfile.attribute.annotation.Annotation; import proguard.classfile.attribute.annotation.AnnotationDefaultAttribute; import proguard.classfile.attribute.annotation.AnnotationElementValue; import proguard.classfile.attribute.annotation.AnnotationsAttribute; import proguard.classfile.attribute.annotation.ArrayElementValue; import proguard.classfile.attribute.annotation.ClassElementValue; import proguard.classfile.attribute.annotation.ConstantElementValue; import proguard.classfile.attribute.annotation.EnumConstantElementValue; import proguard.classfile.attribute.annotation.ParameterAnnotationsAttribute; import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor; import proguard.classfile.attribute.module.ExportsInfo; import proguard.classfile.attribute.module.ModuleAttribute; import proguard.classfile.attribute.module.ModuleMainClassAttribute; import proguard.classfile.attribute.module.ModulePackagesAttribute; import proguard.classfile.attribute.module.OpensInfo; import proguard.classfile.attribute.module.ProvidesInfo; import proguard.classfile.attribute.module.RequiresInfo; import proguard.classfile.attribute.module.visitor.ExportsInfoVisitor; import proguard.classfile.attribute.module.visitor.OpensInfoVisitor; import proguard.classfile.attribute.module.visitor.ProvidesInfoVisitor; import proguard.classfile.attribute.module.visitor.RequiresInfoVisitor; import proguard.classfile.attribute.preverification.FullFrame; import proguard.classfile.attribute.preverification.MoreZeroFrame; import proguard.classfile.attribute.preverification.ObjectType; import proguard.classfile.attribute.preverification.SameOneFrame; import proguard.classfile.attribute.preverification.StackMapAttribute; import proguard.classfile.attribute.preverification.StackMapFrame; import proguard.classfile.attribute.preverification.StackMapTableAttribute; import proguard.classfile.attribute.preverification.VerificationType; import proguard.classfile.attribute.preverification.visitor.StackMapFrameVisitor; import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.attribute.visitor.BootstrapMethodInfoVisitor; import proguard.classfile.attribute.visitor.ExceptionInfoVisitor; import proguard.classfile.attribute.visitor.InnerClassesInfoVisitor; import proguard.classfile.attribute.visitor.ParameterInfoVisitor; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.Constant; import proguard.classfile.constant.DoubleConstant; import proguard.classfile.constant.DynamicConstant; import proguard.classfile.constant.FloatConstant; import proguard.classfile.constant.IntegerConstant; import proguard.classfile.constant.InvokeDynamicConstant; import proguard.classfile.constant.LongConstant; import proguard.classfile.constant.MethodHandleConstant; import proguard.classfile.constant.MethodTypeConstant; import proguard.classfile.constant.ModuleConstant; import proguard.classfile.constant.NameAndTypeConstant; import proguard.classfile.constant.PackageConstant; import proguard.classfile.constant.PrimitiveArrayConstant; import proguard.classfile.constant.RefConstant; import proguard.classfile.constant.StringConstant; import proguard.classfile.constant.Utf8Constant; import proguard.classfile.constant.visitor.ConstantTagFilter; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.kotlin.KotlinAnnotatable; import proguard.classfile.kotlin.KotlinAnnotation; import proguard.classfile.kotlin.KotlinClassKindMetadata; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.kotlin.KotlinConstructorMetadata; import proguard.classfile.kotlin.KotlinContractMetadata; import proguard.classfile.kotlin.KotlinDeclarationContainerMetadata; import proguard.classfile.kotlin.KotlinEffectExpressionMetadata; import proguard.classfile.kotlin.KotlinEffectMetadata; import proguard.classfile.kotlin.KotlinFileFacadeKindMetadata; import proguard.classfile.kotlin.KotlinFunctionMetadata; import proguard.classfile.kotlin.KotlinMetadata; import proguard.classfile.kotlin.KotlinMultiFileFacadeKindMetadata; import proguard.classfile.kotlin.KotlinMultiFilePartKindMetadata; import proguard.classfile.kotlin.KotlinPropertyMetadata; import proguard.classfile.kotlin.KotlinSyntheticClassKindMetadata; import proguard.classfile.kotlin.KotlinTypeAliasMetadata; import proguard.classfile.kotlin.KotlinTypeMetadata; import proguard.classfile.kotlin.KotlinTypeParameterMetadata; import proguard.classfile.kotlin.KotlinValueParameterMetadata; import proguard.classfile.kotlin.KotlinVersionRequirementMetadata; import proguard.classfile.kotlin.visitor.KotlinAnnotationVisitor; import proguard.classfile.kotlin.visitor.KotlinConstructorVisitor; import proguard.classfile.kotlin.visitor.KotlinContractVisitor; import proguard.classfile.kotlin.visitor.KotlinEffectExprVisitor; import proguard.classfile.kotlin.visitor.KotlinEffectVisitor; import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.KotlinPropertyVisitor; import proguard.classfile.kotlin.visitor.KotlinTypeAliasVisitor; import proguard.classfile.kotlin.visitor.KotlinTypeParameterVisitor; import proguard.classfile.kotlin.visitor.KotlinTypeVisitor; import proguard.classfile.kotlin.visitor.KotlinValueParameterVisitor; import proguard.classfile.kotlin.visitor.KotlinVersionRequirementVisitor; import proguard.classfile.kotlin.visitor.MemberToKotlinPropertyVisitor; import proguard.classfile.kotlin.visitor.MethodToKotlinConstructorVisitor; import proguard.classfile.kotlin.visitor.MethodToKotlinFunctionVisitor; import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.ClassAccessFilter; import proguard.classfile.visitor.ClassHierarchyTraveler; import proguard.classfile.visitor.ClassVisitor; import proguard.classfile.visitor.ConcreteClassDownTraveler; import proguard.classfile.visitor.MemberAccessFilter; import proguard.classfile.visitor.MemberToClassVisitor; import proguard.classfile.visitor.MemberVisitor; import proguard.classfile.visitor.MultiMemberVisitor; import proguard.classfile.visitor.NamedMethodVisitor; import proguard.classfile.visitor.ProgramClassFilter; import proguard.classfile.visitor.ReferencedClassVisitor; import proguard.fixer.kotlin.KotlinAnnotationCounter; import proguard.util.Processable; import proguard.util.ProcessingFlags; import java.util.List; /** * This ClassVisitor, MemberVisitor and KotlinMetadataVisitor recursively marks all classes and class * elements that are being used. * * @see InterfaceUsageMarker * @see ClassShrinker * * @author Eric Lafortune */ public class ClassUsageMarker implements ClassVisitor, MemberVisitor, KotlinMetadataVisitor, ConstantVisitor, AttributeVisitor, InnerClassesInfoVisitor, ExceptionInfoVisitor, StackMapFrameVisitor, VerificationTypeVisitor, ParameterInfoVisitor, // LocalVariableInfoVisitor, // LocalVariableTypeInfoVisitor, // AnnotationVisitor, ElementValueVisitor, RequiresInfoVisitor, ExportsInfoVisitor, OpensInfoVisitor, ProvidesInfoVisitor, InstructionVisitor { // A processing info flag to indicate the ProgramMember object is being used, // if its Clazz can be determined as being used as well. private final Object POSSIBLY_USED = new Object(); private final MarkingMode markingMode; private final SimpleUsageMarker usageMarker; private final KotlinUsageMarker kotlinUsageMarker = new KotlinUsageMarker(); private final ClassVisitor interfaceUsageMarker = new MyInterfaceUsageMarker(); private final MyDefaultMethodUsageMarker defaultMethodUsageMarker = new MyDefaultMethodUsageMarker(); private final MyPossiblyUsedMemberUsageMarker possiblyUsedMemberUsageMarker = new MyPossiblyUsedMemberUsageMarker(); private final MemberVisitor nonEmptyMethodUsageMarker = new AllAttributeVisitor( new MyNonEmptyMethodUsageMarker()); private final ConstantVisitor parameterlessConstructorMarker = new ConstantTagFilter(new int[] { Constant.STRING, Constant.CLASS }, new ReferencedClassVisitor( new NamedMethodVisitor(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, this))); private ConstantVisitor extraConstantVisitor; private MemberVisitor extraMethodVisitor; public enum MarkingMode { SHRINKING, MAIN_DEX_TRACING } /** * Creates a new UsageMarker. It only marks interfaces if they are * really used in the code. */ public ClassUsageMarker() { this(new SimpleUsageMarker(), MarkingMode.SHRINKING); } /** * Creates a new UsageMarker. It only marks interfaces if they are * really used in the code. */ public ClassUsageMarker(SimpleUsageMarker usageMarker) { this(usageMarker, MarkingMode.SHRINKING); } /** * Creates a new UsageMarker. * @param markingMode specifies which type of marking is done */ public ClassUsageMarker(SimpleUsageMarker usageMarker, MarkingMode markingMode) { this.usageMarker = usageMarker; this.markingMode = markingMode; } /** * Returns the SimpleUsageMarker used by this class to mark the individual classes, class members, ... */ public SimpleUsageMarker getUsageMarker() { return usageMarker; } /** * Sets an optional ConstantVisitor that is invoked for all IntegerConstant * and StringConstant instances that are marked. */ public void setExtraConstantVisitor(ConstantVisitor extraConstantVisitor) { this.extraConstantVisitor = extraConstantVisitor; } /** * Return the optional ConstantVisitor that is invoked for all IntegerConstant * and StringConstant instances that are marked. */ public ConstantVisitor getExtraConstantVisitor() { return extraConstantVisitor; } /** * Sets an optional MemberVisitor that is invoked for all methods * that are marked. */ public void setExtraMethodVisitor(MemberVisitor extraMethodVisitor) { this.extraMethodVisitor = extraMethodVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { if (shouldBeMarkedAsUsed(programClass)) { // Mark this class. markAsUsed(programClass); markProgramClassBody(programClass); // Mark the Kotlin metadata. programClass.accept(new ReferencedKotlinMetadataVisitor(kotlinUsageMarker)); } } protected void markProgramClassBody(ProgramClass programClass) { // Mark this class's name. markConstant(programClass, programClass.u2thisClass); // Mark the superclass. markOptionalConstant(programClass, programClass.u2superClass); // Give the interfaces preliminary marks. programClass.hierarchyAccept(false, false, true, false, interfaceUsageMarker); // Explicitly mark the method, if it's not empty. programClass.methodAccept(ClassConstants.METHOD_NAME_CLINIT, ClassConstants.METHOD_TYPE_CLINIT, nonEmptyMethodUsageMarker); // Process all class members that have already been marked as possibly used. programClass.fieldsAccept(possiblyUsedMemberUsageMarker); programClass.methodsAccept(possiblyUsedMemberUsageMarker); // Mark the attributes. programClass.attributesAccept(this); } @Override public void visitLibraryClass(LibraryClass libraryClass) { if (shouldBeMarkedAsUsed(libraryClass)) { markAsUsed(libraryClass); // We're not going to analyze all library code. We're assuming that // if this class is being used, all of its methods will be used as // well. We'll mark them as such (here and in all subclasses). // Mark the superclass. Clazz superClass = libraryClass.superClass; if (superClass != null) { superClass.accept(this); } // Mark the interfaces. Clazz[] interfaceClasses = libraryClass.interfaceClasses; if (interfaceClasses != null) { for (int index = 0; index < interfaceClasses.length; index++) { if (interfaceClasses[index] != null) { interfaceClasses[index].accept(this); } } } // Mark all methods. When tracing the main dex we expect that if the methods are used, // their classes should be loaded anyway, so we don't need to analyse the contents of overrides. if (markingMode != MarkingMode.MAIN_DEX_TRACING) { libraryClass.methodsAccept(this); } } } // Implementations for KotlinMetadataVisitor. @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) { // Only grow the Kotlin metadata usage tree for used classes. if (isUsed(clazz)) { kotlinMetadata.accept(clazz, kotlinUsageMarker); // // Check if any annotations should be kept. // kotlinMetadata.accept(clazz, // new AllKotlinAnnotationVisitor(kotlinUsageMarker)); // // // Check if any types should be kept. // kotlinMetadata.accept(clazz, // new AllTypeVisitor(kotlinUsageMarker)); } } @Override public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) { // Check whether type aliases have a core type that's kept. // Don't go into the specific subtypes of declaration containers, // because we're only interested in type aliases and other members // won't be marked anyway. kotlinUsageMarker.visitKotlinDeclarationContainerMetadata(clazz, kotlinDeclarationContainerMetadata); visitAnyKotlinMetadata(clazz, kotlinDeclarationContainerMetadata); } /** * This ClassVisitor marks ProgramClass objects as possibly used, * and it visits LibraryClass objects with its outer UsageMarker. */ private class MyInterfaceUsageMarker implements ClassVisitor { @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { if (shouldBeMarkedAsPossiblyUsed(programClass)) { // We can't process the interface yet, because it might not // be required. Give it a preliminary mark. markAsPossiblyUsed(programClass); } } @Override public void visitLibraryClass(LibraryClass libraryClass) { // Make sure all library interface methods are marked. ClassUsageMarker.this.visitLibraryClass(libraryClass); } } /** * This MemberVisitor marks ProgramMethod objects of default * implementations that may be present in interface classes. */ private class MyDefaultMethodUsageMarker implements MemberVisitor { // Implementations for MemberVisitor. @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (shouldBeMarkedAsUsed(programMethod)) { markAsUsed(programMethod); // Mark the method body. markProgramMethodBody(programClass, programMethod); // Note that, if the method has been marked as possibly used, // the method hierarchy has already been marked (cfr. below). } } } /** * This MemberVisitor marks ProgramField and ProgramMethod objects that * have already been marked as possibly used. */ private class MyPossiblyUsedMemberUsageMarker implements MemberVisitor { // Implementations for MemberVisitor. @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Has the method already been referenced? if (isPossiblyUsed(programField)) { markAsUsed(programField); // Mark the name and descriptor. markConstant(programClass, programField.u2nameIndex); markConstant(programClass, programField.u2descriptorIndex); // Mark the attributes. programField.attributesAccept(programClass, ClassUsageMarker.this); // Mark the classes referenced in the descriptor string. programField.referencedClassesAccept(ClassUsageMarker.this); } } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Has the method already been referenced? if (isPossiblyUsed(programMethod)) { markAsUsed(programMethod); // Mark the method body. markProgramMethodBody(programClass, programMethod); // Note that, if the method has been marked as possibly used, // the method hierarchy has already been marked (cfr. below). } } } /** * This AttributeVisitor marks ProgramMethod objects of non-empty methods. */ private class MyNonEmptyMethodUsageMarker implements AttributeVisitor { // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { if (codeAttribute.u4codeLength > 1) { method.accept(clazz, ClassUsageMarker.this); } } } // Implementations for MemberVisitor. @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { if (shouldBeMarkedAsUsed(programField)) { // Is the field's class used? if (isUsed(programClass)) { markAsUsed(programField); // Mark the field body. markProgramFieldBody(programClass, programField); } // Hasn't the field been marked as possibly being used yet? else if (shouldBeMarkedAsPossiblyUsed(programClass, programField)) { // We can't process the field yet, because the class isn't // marked as being used (yet). Give it a preliminary mark. markAsPossiblyUsed(programField); } } } @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (shouldBeMarkedAsUsed(programMethod)) { // Is the method's class used? if (isUsed(programClass)) { markAsUsed(programMethod); // Mark the method body. markProgramMethodBody(programClass, programMethod); // Mark the method hierarchy. markMethodHierarchy(programClass, programMethod); } // Hasn't the method been marked as possibly being used yet? else if (shouldBeMarkedAsPossiblyUsed(programClass, programMethod)) { // We can't process the method yet, because the class isn't // marked as being used (yet). Give it a preliminary mark. markAsPossiblyUsed(programMethod); // Mark the method hierarchy. markMethodHierarchy(programClass, programMethod); } } } @Override public void visitLibraryField(LibraryClass programClass, LibraryField programField) {} @Override public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { if (shouldBeMarkedAsUsed(libraryMethod)) { markAsUsed(libraryMethod); // Mark the method hierarchy. markMethodHierarchy(libraryClass, libraryMethod); if (extraMethodVisitor != null) { libraryMethod.accept(libraryClass, extraMethodVisitor); } markMethodKotlinMetadata(libraryClass, libraryMethod); } } protected void markProgramFieldBody(ProgramClass programClass, ProgramField programField) { // Mark the name and descriptor. markConstant(programClass, programField.u2nameIndex); markConstant(programClass, programField.u2descriptorIndex); // Mark the attributes. programField.attributesAccept(programClass, this); // Mark the classes referenced in the descriptor string. programField.referencedClassesAccept(this); programField.accept(programClass, new MemberToKotlinPropertyVisitor(kotlinUsageMarker)); } protected void markProgramMethodBody(ProgramClass programClass, ProgramMethod programMethod) { // Mark the name and descriptor. markConstant(programClass, programMethod.u2nameIndex); markConstant(programClass, programMethod.u2descriptorIndex); // Mark the attributes. programMethod.attributesAccept(programClass, this); // Mark the classes referenced in the descriptor string. programMethod.referencedClassesAccept(this); if (extraMethodVisitor != null) { programMethod.accept(programClass, extraMethodVisitor); } markMethodKotlinMetadata(programClass, programMethod); } private void markMethodKotlinMetadata(Clazz clazz, Method method) { if (method.getName(clazz).equals(ClassConstants.METHOD_NAME_INIT)) { method.accept(clazz, new MethodToKotlinConstructorVisitor(kotlinUsageMarker)); } else { method.accept(clazz, new MultiMemberVisitor( new MethodToKotlinFunctionVisitor(kotlinUsageMarker), new MemberToKotlinPropertyVisitor(kotlinUsageMarker))); } } /** * Marks the hierarchy of implementing or overriding methods corresponding * to the given method, if any. */ protected void markMethodHierarchy(Clazz clazz, Method method) { // Only visit the hierarchy if the method is not private, static, or // an initializer. int accessFlags = method.getAccessFlags(); if ((accessFlags & (AccessConstants.PRIVATE | AccessConstants.STATIC)) == 0 && !ClassUtil.isInitializer(method.getName(clazz))) { // We can skip private and static methods in the hierarchy, and // also abstract methods, unless they might widen a current // non-public access. int requiredUnsetAccessFlags = AccessConstants.PRIVATE | AccessConstants.STATIC | ((accessFlags & AccessConstants.PUBLIC) == 0 ? 0 : AccessConstants.ABSTRACT); // Mark default implementations in interfaces down the hierarchy, // if this is an interface itself. // TODO: This may be premature if there aren't any concrete implementing classes. clazz.accept(new ClassAccessFilter(AccessConstants.INTERFACE, 0, new ClassHierarchyTraveler(false, false, false, true, new ProgramClassFilter( new ClassAccessFilter(AccessConstants.INTERFACE, 0, new NamedMethodVisitor(method.getName(clazz), method.getDescriptor(clazz), new MemberAccessFilter(0, requiredUnsetAccessFlags, defaultMethodUsageMarker))))))); // Mark other implementations. clazz.accept(new ConcreteClassDownTraveler( new ClassHierarchyTraveler(true, true, false, true, new NamedMethodVisitor(method.getName(clazz), method.getDescriptor(clazz), new MemberAccessFilter(0, requiredUnsetAccessFlags, this))))); } } // Implementations for ConstantVisitor. @Override public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant) { if (shouldBeMarkedAsUsed(integerConstant)) { markAsUsed(integerConstant); // Also apply the optional extra constant visitor. if (extraConstantVisitor != null) { extraConstantVisitor.visitIntegerConstant(clazz, integerConstant); } } } @Override public void visitLongConstant(Clazz clazz, LongConstant longConstant) { if (shouldBeMarkedAsUsed(longConstant)) { markAsUsed(longConstant); } } @Override public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant) { if (shouldBeMarkedAsUsed(floatConstant)) { markAsUsed(floatConstant); } } @Override public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant) { if (shouldBeMarkedAsUsed(doubleConstant)) { markAsUsed(doubleConstant); } } @Override public void visitPrimitiveArrayConstant(Clazz clazz, PrimitiveArrayConstant primitiveArrayConstant) { if (shouldBeMarkedAsUsed(primitiveArrayConstant)) { markAsUsed(primitiveArrayConstant); } // Also apply the optional extra constant visitor. if (extraConstantVisitor != null) { extraConstantVisitor.visitPrimitiveArrayConstant(clazz, primitiveArrayConstant); } } @Override public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { if (shouldBeMarkedAsUsed(stringConstant)) { markAsUsed(stringConstant); markConstant(clazz, stringConstant.u2stringIndex); // Mark the referenced class and class member, if any. stringConstant.referencedClassAccept(this); stringConstant.referencedMemberAccept(this); // Also apply the optional extra constant visitor. if (extraConstantVisitor != null) { extraConstantVisitor.visitStringConstant(clazz, stringConstant); } } } @Override public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { if (shouldBeMarkedAsUsed(utf8Constant)) { markAsUsed(utf8Constant); } } @Override public void visitDynamicConstant(Clazz clazz, DynamicConstant dynamicConstant) { if (shouldBeMarkedAsUsed(dynamicConstant)) { markAsUsed(dynamicConstant); markConstant(clazz, dynamicConstant.u2nameAndTypeIndex); // Mark the referenced descriptor classes. dynamicConstant.referencedClassesAccept(this); // Mark the bootstrap methods attribute. clazz.attributesAccept(new MyBootStrapMethodUsageMarker(dynamicConstant.u2bootstrapMethodAttributeIndex)); } } @Override public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { if (shouldBeMarkedAsUsed(invokeDynamicConstant)) { markAsUsed(invokeDynamicConstant); markConstant(clazz, invokeDynamicConstant.u2nameAndTypeIndex); // Mark the referenced descriptor classes. invokeDynamicConstant.referencedClassesAccept(this); // Mark the bootstrap methods attribute. clazz.attributesAccept(new MyBootStrapMethodUsageMarker(invokeDynamicConstant.u2bootstrapMethodAttributeIndex)); } } @Override public void visitMethodHandleConstant(Clazz clazz, MethodHandleConstant methodHandleConstant) { if (shouldBeMarkedAsUsed(methodHandleConstant)) { markAsUsed(methodHandleConstant); markConstant(clazz, methodHandleConstant.u2referenceIndex); } } @Override public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) { if (shouldBeMarkedAsUsed(refConstant)) { markAsUsed(refConstant); markConstant(clazz, refConstant.u2classIndex); markConstant(clazz, refConstant.u2nameAndTypeIndex); // When compiled with "-target 1.2" or higher, the class or // interface actually containing the referenced class member may // be higher up the hierarchy. Make sure it's marked, in case it // isn't used elsewhere. refConstant.referencedClassAccept(this); // Mark the referenced class member itself. refConstant.referencedMemberAccept(this); } } @Override public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { if (shouldBeMarkedAsUsed(classConstant)) { markAsUsed(classConstant); markConstant(clazz, classConstant.u2nameIndex); // Mark the referenced class itself. classConstant.referencedClassAccept(this); } } @Override public void visitMethodTypeConstant(Clazz clazz, MethodTypeConstant methodTypeConstant) { if (shouldBeMarkedAsUsed(methodTypeConstant)) { markAsUsed(methodTypeConstant); markConstant(clazz, methodTypeConstant.u2descriptorIndex); // Mark the referenced descriptor classes. methodTypeConstant.referencedClassesAccept(this); } } @Override public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant) { if (shouldBeMarkedAsUsed(nameAndTypeConstant)) { markAsUsed(nameAndTypeConstant); markConstant(clazz, nameAndTypeConstant.u2nameIndex); markConstant(clazz, nameAndTypeConstant.u2descriptorIndex); } } @Override public void visitModuleConstant(Clazz clazz, ModuleConstant moduleConstant) { if (shouldBeMarkedAsUsed(moduleConstant)) { markAsUsed(moduleConstant); markConstant(clazz, moduleConstant.u2nameIndex); } } @Override public void visitPackageConstant(Clazz clazz, PackageConstant packageConstant) { if (shouldBeMarkedAsUsed(packageConstant)) { markAsUsed(packageConstant); markConstant(clazz, packageConstant.u2nameIndex); } } /** * This AttributeVisitor marks the bootstrap methods attributes, their * method entries, their method handles, and their arguments. */ private class MyBootStrapMethodUsageMarker implements AttributeVisitor, BootstrapMethodInfoVisitor { private int bootstrapMethodIndex; private MyBootStrapMethodUsageMarker(int bootstrapMethodIndex) { this.bootstrapMethodIndex = bootstrapMethodIndex; } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { if (shouldBeMarkedAsUsed(bootstrapMethodsAttribute)) { markAsUsed(bootstrapMethodsAttribute); markConstant(clazz, bootstrapMethodsAttribute.u2attributeNameIndex); } bootstrapMethodsAttribute.bootstrapMethodEntryAccept(clazz, bootstrapMethodIndex, this); } // Implementations for BootstrapMethodInfoVisitor. @Override public void visitBootstrapMethodInfo(Clazz clazz, BootstrapMethodInfo bootstrapMethodInfo) { markAsUsed(bootstrapMethodInfo); markConstant(clazz, bootstrapMethodInfo.u2methodHandleIndex); // Mark the constant pool entries referenced by the arguments. bootstrapMethodInfo.methodArgumentsAccept(clazz, ClassUsageMarker.this); } } // Implementations for AttributeVisitor. // Note that attributes are typically only referenced once, so we don't // test if they have been marked already. @Override public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute) { // This is the best we can do for unknown attributes. markAsUsed(unknownAttribute); markConstant(clazz, unknownAttribute.u2attributeNameIndex); } @Override public void visitSourceDebugExtensionAttribute(Clazz clazz, SourceDebugExtensionAttribute sourceDebugExtensionAttribute) { markAsUsed(sourceDebugExtensionAttribute); markConstant(clazz, sourceDebugExtensionAttribute.u2attributeNameIndex); } @Override public void visitRecordAttribute(Clazz clazz, RecordAttribute recordAttribute) { markAsUsed(recordAttribute); markConstant(clazz, recordAttribute.u2attributeNameIndex); // Don't mark the components yet. We may mark them later, in // RecordComponentUsageMarker. //recordAttribute.componentsAccept(clazz, this); } @Override public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { // Don't mark the attribute and its name here. We may mark it in // MyBootStrapMethodsAttributeUsageMarker. } @Override public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute) { markAsUsed(sourceFileAttribute); markConstant(clazz, sourceFileAttribute.u2attributeNameIndex); markConstant(clazz, sourceFileAttribute.u2sourceFileIndex); } @Override public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute) { markAsUsed(sourceDirAttribute); markConstant(clazz, sourceDirAttribute.u2attributeNameIndex); markConstant(clazz, sourceDirAttribute.u2sourceDirIndex); } @Override public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { // Don't mark the attribute and its name yet. We may mark it later, in // InnerUsageMarker. //markAsUsed(innerClassesAttribute); //markConstant(clazz, innerClassesAttribute.u2attrNameIndex); // Do mark the outer class entries. innerClassesAttribute.innerClassEntriesAccept(clazz, this); } @Override public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) { markAsUsed(enclosingMethodAttribute); markConstant( clazz, enclosingMethodAttribute.u2attributeNameIndex); markConstant( clazz, enclosingMethodAttribute.u2classIndex); markOptionalConstant(clazz, enclosingMethodAttribute.u2nameAndTypeIndex); } @Override public void visitNestHostAttribute(Clazz clazz, NestHostAttribute nestHostAttribute) { // Don't mark the attribute and its contents yet. We may mark it later, // in NestUsageMarker. //markAsUsed(nestHostAttribute); //markConstant(clazz, nestHostAttribute.u2attributeNameIndex); //markConstant(clazz, nestHostAttribute.u2hostClassIndex); } @Override public void visitNestMembersAttribute(Clazz clazz, NestMembersAttribute nestMembersAttribute) { // Don't mark the attribute and its contents yet. We may mark it later, // in NestUsageMarker. //markAsUsed(nestMembersAttribute); //markConstant(clazz, nestMembersAttribute.u2attributeNameIndex); // Mark the nest member entries. //nestMembersAttribute.memberClassConstantsAccept(clazz, this); } @Override public void visitPermittedSubclassesAttribute(Clazz clazz, PermittedSubclassesAttribute permittedSubclassesAttribute) { // Don't mark the attribute and its contents yet. We may mark it later, // in NestUsageMarker. //markAsUsed(permittedSubclassesAttribute); //markConstant(clazz, permittedSubclassesAttribute.u2attributeNameIndex); // Mark the nest member entries. //permittedSubclassesAttribute.memberClassConstantsAccept(clazz, this); } @Override public void visitModuleAttribute(Clazz clazz, ModuleAttribute moduleAttribute) { markAsUsed(moduleAttribute); markConstant( clazz, moduleAttribute.u2attributeNameIndex); markConstant( clazz, moduleAttribute.u2moduleNameIndex); markOptionalConstant(clazz, moduleAttribute.u2moduleVersionIndex); // Mark the constant pool entries referenced by the contained info. moduleAttribute.requiresAccept(clazz, this); moduleAttribute.exportsAccept(clazz, this); moduleAttribute.opensAccept(clazz, this); markConstants(clazz, moduleAttribute.u2uses, moduleAttribute.u2usesCount); // Mark the constant pool entries referenced by the provides info. moduleAttribute.providesAccept(clazz, this); } @Override public void visitModuleMainClassAttribute(Clazz clazz, ModuleMainClassAttribute moduleMainClassAttribute) { markAsUsed(moduleMainClassAttribute); markConstant(clazz, moduleMainClassAttribute.u2attributeNameIndex); markConstant(clazz, moduleMainClassAttribute.u2mainClass); } @Override public void visitModulePackagesAttribute(Clazz clazz, ModulePackagesAttribute modulePackagesAttribute) { markAsUsed(modulePackagesAttribute); markConstant(clazz, modulePackagesAttribute.u2attributeNameIndex); // Mark the constant pool entries referenced by the packages info. modulePackagesAttribute.packagesAccept(clazz, this); } @Override public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute) { markAsUsed(deprecatedAttribute); markConstant(clazz, deprecatedAttribute.u2attributeNameIndex); } @Override public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute) { markAsUsed(syntheticAttribute); markConstant(clazz, syntheticAttribute.u2attributeNameIndex); } @Override public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) { markAsUsed(signatureAttribute); markConstant(clazz, signatureAttribute.u2attributeNameIndex); markConstant(clazz, signatureAttribute.u2signatureIndex); // Don't mark the referenced classes. We'll clean them up in // ClassShrinker, if they appear unused. //// Mark the classes referenced in the descriptor string. //signatureAttribute.referencedClassesAccept(this); } @Override public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute) { markAsUsed(constantValueAttribute); markConstant(clazz, constantValueAttribute.u2attributeNameIndex); markConstant(clazz, constantValueAttribute.u2constantValueIndex); } @Override public void visitMethodParametersAttribute(Clazz clazz, Method method, MethodParametersAttribute methodParametersAttribute) { markAsUsed(methodParametersAttribute); markConstant(clazz, methodParametersAttribute.u2attributeNameIndex); // Mark the constant pool entries referenced by the parameter information. methodParametersAttribute.parametersAccept(clazz, method, this); } @Override public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute) { markAsUsed(exceptionsAttribute); markConstant(clazz, exceptionsAttribute.u2attributeNameIndex); // Mark the constant pool entries referenced by the exceptions. exceptionsAttribute.exceptionEntriesAccept(clazz, this); } @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { markAsUsed(codeAttribute); markConstant(clazz, codeAttribute.u2attributeNameIndex); // Mark the constant pool entries referenced by the instructions, // by the exceptions, and by the attributes. codeAttribute.instructionsAccept(clazz, method, this); codeAttribute.exceptionsAccept(clazz, method, this); codeAttribute.attributesAccept(clazz, method, this); } @Override public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute) { markAsUsed(stackMapAttribute); markConstant(clazz, stackMapAttribute.u2attributeNameIndex); // Mark the constant pool entries referenced by the stack map frames. stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); } @Override public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute) { markAsUsed(stackMapTableAttribute); markConstant(clazz, stackMapTableAttribute.u2attributeNameIndex); // Mark the constant pool entries referenced by the stack map frames. stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); } @Override public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) { markAsUsed(lineNumberTableAttribute); markConstant(clazz, lineNumberTableAttribute.u2attributeNameIndex); } @Override public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { // Don't mark the attribute and its contents yet. We may mark them later, // in LocalVariableTypeUsageMarker. //markAsUsed(localVariableTableAttribute); // //markConstant(clazz, localVariableTableAttribute.u2attributeNameIndex); // //// Mark the constant pool entries referenced by the local variables. //localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } @Override public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { // Don't mark the attribute and its contents yet. We may mark them later, // in LocalVariableTypeUsageMarker. //markAsUsed(localVariableTypeTableAttribute); // //markConstant(clazz, localVariableTypeTableAttribute.u2attributeNameIndex); // //// Mark the constant pool entries referenced by the local variable types. //localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } @Override public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) { // Don't mark the attribute and its contents yet. We may mark them later, // in AnnotationUsageMarker. //markAsUsed(annotationsAttribute); // //markConstant(clazz, annotationsAttribute.u2attributeNameIndex); // //// Mark the constant pool entries referenced by the annotations. //annotationsAttribute.annotationsAccept(clazz, this); } @Override public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) { // Don't mark the attribute and its contents yet. We may mark them later, // in AnnotationUsageMarker. //markAsUsed(parameterAnnotationsAttribute); // //markConstant(clazz, parameterAnnotationsAttribute.u2attributeNameIndex); // //// Mark the constant pool entries referenced by the annotations. //parameterAnnotationsAttribute.annotationsAccept(clazz, method, this); } @Override public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) { // Don't mark the attribute and its contents yet. We may mark them later, // in AnnotationUsageMarker. //markAsUsed(annotationDefaultAttribute); // //markConstant(clazz, annotationDefaultAttribute.u2attributeNameIndex); // // Mark the constant pool entries referenced by the element value. annotationDefaultAttribute.defaultValueAccept(clazz, this); } // Implementations for ExceptionInfoVisitor. @Override public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) { markAsUsed(exceptionInfo); markOptionalConstant(clazz, exceptionInfo.u2catchType); } // Implementations for InnerClassesInfoVisitor. @Override public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { // At this point, we only mark outer classes of this class. // Inner class can be marked later, by InnerUsageMarker. if (innerClassesInfo.u2innerClassIndex != 0 && clazz.getName().equals(clazz.getClassName(innerClassesInfo.u2innerClassIndex))) { markAsUsed(innerClassesInfo); // Mark the constant pool entries referenced by the contained info. innerClassesInfo.innerClassConstantAccept(clazz, this); innerClassesInfo.outerClassConstantAccept(clazz, this); innerClassesInfo.innerNameConstantAccept(clazz, this); } } // Implementations for StackMapFrameVisitor. @Override public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame) {} @Override public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame) { // Mark the constant pool entries referenced by the verification types. sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this); } @Override public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame) { // Mark the constant pool entries referenced by the verification types. moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this); } @Override public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame) { // Mark the constant pool entries referenced by the verification types. fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this); fullFrame.stackAccept(clazz, method, codeAttribute, offset, this); } // Implementations for VerificationTypeVisitor. @Override public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {} @Override public void visitObjectType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ObjectType objectType) { markConstant(clazz, objectType.u2classIndex); } // Implementations for ParameterInfoVisitor. @Override public void visitParameterInfo(Clazz clazz, Method method, int parameterIndex, ParameterInfo parameterInfo) { parameterInfo.nameConstantAccept(clazz, this); } // Implementations for RequiresInfoVisitor. @Override public void visitRequiresInfo(Clazz clazz, RequiresInfo requiresInfo) { markConstant( clazz, requiresInfo.u2requiresIndex); markOptionalConstant(clazz, requiresInfo.u2requiresVersionIndex); } // Implementations for ExportsInfoVisitor. @Override public void visitExportsInfo(Clazz clazz, ExportsInfo exportsInfo) { markConstant( clazz, exportsInfo.u2exportsIndex); markConstants(clazz, exportsInfo.u2exportsToIndex, exportsInfo.u2exportsToCount); } // Implementations for OpensInfoVisitor. @Override public void visitOpensInfo(Clazz clazz, OpensInfo opensInfo) { markConstant( clazz, opensInfo.u2opensIndex); markConstants(clazz, opensInfo.u2opensToIndex, opensInfo.u2opensToCount); } // Implementations for ProvidesInfoVisitor. @Override public void visitProvidesInfo(Clazz clazz, ProvidesInfo providesInfo) { markConstant( clazz, providesInfo.u2providesIndex); markConstants(clazz, providesInfo.u2providesWithIndex, providesInfo.u2providesWithCount); } // // Implementations for LocalVariableInfoVisitor. // // public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) // { // markConstant(clazz, localVariableInfo.u2nameIndex); // markConstant(clazz, localVariableInfo.u2descriptorIndex); // } // // // // Implementations for LocalVariableTypeInfoVisitor. // // public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) // { // markConstant(clazz, localVariableTypeInfo.u2nameIndex); // markConstant(clazz, localVariableTypeInfo.u2signatureIndex); // } // // // // Implementations for AnnotationVisitor. // // public void visitAnnotation(Clazz clazz, Annotation annotation) // { // markConstant(clazz, annotation.u2typeIndex); // // // Mark the constant pool entries referenced by the element values. // annotation.elementValuesAccept(clazz, this); // } // // // Implementations for ElementValueVisitor. public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) { // markOptionalConstant(clazz, constantElementValue.u2elementNameIndex); // markConstant( clazz, constantElementValue.u2constantValueIndex); } public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) { // markOptionalConstant(clazz, enumConstantElementValue.u2elementNameIndex); // markConstant( clazz, enumConstantElementValue.u2typeNameIndex); // markConstant( clazz, enumConstantElementValue.u2constantNameIndex); // Mark the referenced field as used. enumConstantElementValue.referencedFieldAccept(this); } public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) { // markOptionalConstant(clazz, classElementValue.u2elementNameIndex); // // // Mark the referenced class constant pool entry. // markConstant(clazz, classElementValue.u2classInfoIndex); } public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) { // markOptionalConstant(clazz, annotationElementValue.u2elementNameIndex); // // // Mark the constant pool entries referenced by the annotation. // annotationElementValue.annotationAccept(clazz, this); } public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) { // markOptionalConstant(clazz, arrayElementValue.u2elementNameIndex); // // // Mark the constant pool entries referenced by the element values. // arrayElementValue.elementValuesAccept(clazz, annotation, this); } // Implementations for InstructionVisitor. @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { markConstant(clazz, constantInstruction.constantIndex); // Also mark the parameterless constructor of the class, in case the // string constant or class constant is being used in a Class.forName // or a .class construct. clazz.constantPoolEntryAccept(constantInstruction.constantIndex, parameterlessConstructorMarker); } // Small utility methods. /** * Marks the given processable as being used. */ public void markAsUsed(Processable processable) { usageMarker.markAsUsed(processable); } /** * Returns whether the given program class should still be marked as * being used. */ public boolean shouldBeMarkedAsUsed(ProgramClass programClass) { return shouldBeMarkedAsUsed((Processable)programClass); } /** * Returns whether the given program member should still be marked as * being used. */ public boolean shouldBeMarkedAsUsed(ProgramClass programClass, ProgramMember programMember) { return shouldBeMarkedAsUsed(programMember); } /** * Returns whether the given processable should still be marked as * being used. */ public boolean shouldBeMarkedAsUsed(Processable processable) { return !isUsed(processable); } /** * Returns whether the given processable has been marked as being used. */ public boolean isUsed(Processable processable) { return usageMarker.isUsed(processable); } /** * Marks the given processable as possibly being used. */ public void markAsPossiblyUsed(Processable processable) { usageMarker.markAsPossiblyUsed(processable); } /** * Returns whether the given program member should still be marked as * being used. */ public boolean shouldBeMarkedAsPossiblyUsed(ProgramClass programClass, ProgramMember programMember) { return shouldBeMarkedAsPossiblyUsed(programMember); } /** * Returns whether the given processable should still be marked as * possibly being used. */ public boolean shouldBeMarkedAsPossiblyUsed(Processable processable) { return !isUsed(processable) && !isPossiblyUsed(processable); } /** * Returns whether the given processable has been marked as possibly * being used. */ public boolean isPossiblyUsed(Processable processable) { return usageMarker.isPossiblyUsed(processable); } /** * Clears any usage marks from the given processable. */ public void markAsUnused(Processable processable) { usageMarker.markAsUnused(processable); } /** * Marks the specified constant pool entries of the given class. * This includes visiting any referenced objects. */ private void markConstants(Clazz clazz, int[] constantIndices, int constantIndicesCount) { for (int index = 0; index < constantIndicesCount; index++) { markConstant(clazz, constantIndices[index]); } } /** * Marks the specified constant pool entry of the given class, if the index * is not 0. This includes visiting any referenced objects. */ private void markOptionalConstant(Clazz clazz, int constantIndex) { if (constantIndex != 0) { markConstant(clazz, constantIndex); } } /** * Marks the specified constant pool entry of the given class. * This includes visiting any referenced objects. */ private void markConstant(Clazz clazz, int constantIndex) { clazz.constantPoolEntryAccept(constantIndex, this); } public class KotlinUsageMarker implements KotlinMetadataVisitor, // Implementation interfaces. KotlinPropertyVisitor, KotlinFunctionVisitor, KotlinTypeAliasVisitor, KotlinTypeVisitor, KotlinConstructorVisitor, KotlinTypeParameterVisitor, KotlinValueParameterVisitor, KotlinVersionRequirementVisitor, KotlinContractVisitor, KotlinEffectVisitor, KotlinEffectExprVisitor, KotlinAnnotationVisitor { // Implementations for KotlinMetadataVisitor. @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) { if (!isUsed(kotlinDeclarationContainerMetadata)) { if (isJavaClassUsed(kotlinDeclarationContainerMetadata.ownerReferencedClass)) { markAsUsed(kotlinDeclarationContainerMetadata); } kotlinDeclarationContainerMetadata.typeAliasesAccept(clazz, this); } if (isUsed(kotlinDeclarationContainerMetadata)) { kotlinDeclarationContainerMetadata.propertiesAccept( clazz, this); kotlinDeclarationContainerMetadata.functionsAccept( clazz, this); kotlinDeclarationContainerMetadata.typeAliasesAccept( clazz, this); kotlinDeclarationContainerMetadata.delegatedPropertiesAccept(clazz, this); } } @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinClassKindMetadata); if (isUsed(kotlinClassKindMetadata)) { kotlinClassKindMetadata.referencedClass.accept(ClassUsageMarker.this); markAsUsed(kotlinClassKindMetadata.superTypes); markAsUsed(kotlinClassKindMetadata.typeParameters); markAsUsed(kotlinClassKindMetadata.underlyingPropertyType); if (kotlinClassKindMetadata.flags.isAnnotationClass) { // Annotation classes have constructors in the metadata but // no corresponding Java constructors. markAsUsed(kotlinClassKindMetadata.constructors); } else { kotlinClassKindMetadata.constructorsAccept(clazz, this); } // Mark the INSTANCE field in object classes. if (kotlinClassKindMetadata.flags.isObject) { clazz.fieldAccept(KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, null, ClassUsageMarker.this); } kotlinClassKindMetadata.superTypesAccept( clazz, this); kotlinClassKindMetadata.typeParametersAccept( clazz, this); kotlinClassKindMetadata.inlineClassUnderlyingPropertyTypeAccept(clazz, this); kotlinClassKindMetadata.contextReceiverTypesAccept( clazz, this); kotlinClassKindMetadata.versionRequirementAccept( clazz, this); } } @Override public void visitKotlinFileFacadeMetadata(Clazz clazz, KotlinFileFacadeKindMetadata kotlinFileFacadeKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinFileFacadeKindMetadata); } @Override public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassKindMetadata kotlinSyntheticClassKindMetadata) { markAsUsed(kotlinSyntheticClassKindMetadata); kotlinSyntheticClassKindMetadata.functionsAccept(clazz, this); } @Override public void visitKotlinMultiFileFacadeMetadata(Clazz clazz, KotlinMultiFileFacadeKindMetadata kotlinMultiFileFacadeKindMetadata) { } @Override public void visitKotlinMultiFilePartMetadata(Clazz clazz, KotlinMultiFilePartKindMetadata kotlinMultiFilePartKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinMultiFilePartKindMetadata); if (isUsed(kotlinMultiFilePartKindMetadata)) { kotlinMultiFilePartKindMetadata.referencedFacadeClass.accept(ClassUsageMarker.this); } } // Implementations for KotlinPropertyVisitor. @Override public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata) { if (!isUsed(kotlinPropertyMetadata)) { boolean backingFieldUsed = kotlinPropertyMetadata.referencedBackingField != null && isUsed(kotlinPropertyMetadata.referencedBackingField); boolean getterUsed = kotlinPropertyMetadata.referencedGetterMethod != null && isUsed(kotlinPropertyMetadata.referencedGetterMethod); boolean setterUsed = kotlinPropertyMetadata.referencedSetterMethod != null && isUsed(kotlinPropertyMetadata.referencedSetterMethod); if (backingFieldUsed || getterUsed || setterUsed) { markAsUsed(kotlinPropertyMetadata); } } if (isUsed(kotlinPropertyMetadata)) { markAsUsed(kotlinPropertyMetadata.receiverType); markAsUsed(kotlinPropertyMetadata.typeParameters); markAsUsed(kotlinPropertyMetadata.setterParameters); markAsUsed(kotlinPropertyMetadata.type); if (kotlinPropertyMetadata.flags.common.hasAnnotations && kotlinPropertyMetadata.syntheticMethodForAnnotations != null) { // Annotations are placed on a synthetic method (e.g. myProperty$annotations()) // so we must ensure that the synthetic method is marked as used, if there // are any used annotations there. KotlinAnnotationCounter annotationCounter = new KotlinAnnotationCounter(ClassUsageMarker.this.usageMarker); kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations.accept( kotlinPropertyMetadata.referencedSyntheticMethodClass, annotationCounter ); if (annotationCounter.getCount() != 0) { kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations.accept( kotlinPropertyMetadata.referencedSyntheticMethodClass, ClassUsageMarker.this ); } } kotlinPropertyMetadata.receiverTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.contextReceiverTypesAccept(clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.setterParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.typeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.versionRequirementAccept( clazz, kotlinDeclarationContainerMetadata, this); } } // Implementations for KotlinFunctionVisitor. @Override public void visitAnyFunction(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { if (!isUsed(kotlinFunctionMetadata)) { if (isUsed(kotlinFunctionMetadata.referencedMethod)) { markAsUsed(kotlinFunctionMetadata); } } if (isUsed(kotlinFunctionMetadata)) { // Mark the required elements. markAsUsed(kotlinFunctionMetadata.receiverType); markAsUsed(kotlinFunctionMetadata.typeParameters); markAsUsed(kotlinFunctionMetadata.valueParameters); markAsUsed(kotlinFunctionMetadata.returnType); markAsUsed(kotlinFunctionMetadata.contracts); // If there is a corresponding default method and the user specifically kept this method, keep it as well. if (kotlinFunctionMetadata.referencedDefaultMethod != null && (kotlinFunctionMetadata.referencedMethod.getProcessingFlags() & ProcessingFlags.DONT_SHRINK) != 0) { kotlinFunctionMetadata.referencedDefaultMethodClass .accept(ClassUsageMarker.this); kotlinFunctionMetadata.referencedDefaultMethod .accept(kotlinFunctionMetadata.referencedDefaultMethodClass, ClassUsageMarker.this); } kotlinFunctionMetadata.receiverTypeAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.contextReceiverTypesAccept(clazz, kotlinMetadata, this); kotlinFunctionMetadata.typeParametersAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.valueParametersAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.returnTypeAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.contractsAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.versionRequirementAccept( clazz, kotlinMetadata, this); } } @Override public void visitFunction(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { visitAnyFunction(clazz, kotlinDeclarationContainerMetadata, kotlinFunctionMetadata); // Non-abstract functions in interfaces should have default implementations, so keep it if the // user kept the original function. if (isUsed(kotlinFunctionMetadata)) { if (kotlinDeclarationContainerMetadata.k == KotlinConstants.METADATA_KIND_CLASS && ((KotlinClassKindMetadata)kotlinDeclarationContainerMetadata).flags.isInterface && !kotlinFunctionMetadata.flags.modality.isAbstract && (kotlinFunctionMetadata.referencedMethod.getProcessingFlags() & ProcessingFlags.DONT_SHRINK) != 0) { kotlinFunctionMetadata.referencedDefaultImplementationMethodAccept( new MultiMemberVisitor( ClassUsageMarker.this, new MemberToClassVisitor(ClassUsageMarker.this) ) ); } } } // Implementations for KotlinTypeAliasVisitor. @Override public void visitTypeAlias(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinTypeAliasMetadata kotlinTypeAliasMetadata) { if (!isUsed(kotlinTypeAliasMetadata)) { // Mark a type alias if its expandedType is used. kotlinTypeAliasMetadata.expandedTypeAccept(clazz, kotlinDeclarationContainerMetadata, this); if (isUsed(kotlinTypeAliasMetadata.expandedType)) { markAsUsed(kotlinTypeAliasMetadata); } } if (isUsed(kotlinTypeAliasMetadata)) { clazz.accept(ClassUsageMarker.this); markAsUsed(kotlinTypeAliasMetadata.typeParameters); markAsUsed(kotlinTypeAliasMetadata.underlyingType); markAsUsed(kotlinTypeAliasMetadata.expandedType); kotlinTypeAliasMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinTypeAliasMetadata.underlyingTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinTypeAliasMetadata.expandedTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinTypeAliasMetadata.versionRequirementAccept(clazz, kotlinDeclarationContainerMetadata, this); } } // Implementations for KotlinTypeVisitor. @Override public void visitAnyType(Clazz clazz, KotlinTypeMetadata kotlinTypeMetadata) { if (!isUsed(kotlinTypeMetadata)) { if (kotlinTypeMetadata.className != null) { if (isJavaClassUsed(kotlinTypeMetadata.referencedClass)) { markAsUsed(kotlinTypeMetadata); } } else if (kotlinTypeMetadata.aliasName != null) { kotlinTypeMetadata.referencedTypeAlias.accept(kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer.ownerReferencedClass, kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer, this); if (isUsed(kotlinTypeMetadata.referencedTypeAlias)) { markAsUsed(kotlinTypeMetadata); } } else { markAsUsed(kotlinTypeMetadata); } } if (isUsed(kotlinTypeMetadata)) { if (kotlinTypeMetadata.className != null) { kotlinTypeMetadata.referencedClass.accept(ClassUsageMarker.this); } else if (kotlinTypeMetadata.aliasName != null && !isUsed(kotlinTypeMetadata.referencedTypeAlias)) { markAsUsed(kotlinTypeMetadata.referencedTypeAlias); kotlinTypeMetadata.referencedTypeAlias.accept(null, null, this); } markAsUsed(kotlinTypeMetadata.typeArguments); markAsUsed(kotlinTypeMetadata.upperBounds); markAsUsed(kotlinTypeMetadata.outerClassType); kotlinTypeMetadata.typeArgumentsAccept(clazz, this); kotlinTypeMetadata.upperBoundsAccept( clazz, this); kotlinTypeMetadata.abbreviationAccept( clazz, this); kotlinTypeMetadata.annotationsAccept( clazz, this); } } //Implementations for KotlinConstructorVisitor. @Override public void visitConstructor(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata, KotlinConstructorMetadata kotlinConstructorMetadata) { if (!isUsed(kotlinConstructorMetadata)) { if (isUsed(kotlinConstructorMetadata.referencedMethod)) { markAsUsed(kotlinConstructorMetadata); } } if (isUsed(kotlinConstructorMetadata)) { markAsUsed(kotlinConstructorMetadata.valueParameters); kotlinConstructorMetadata.valueParametersAccept( clazz, kotlinClassKindMetadata, this); kotlinConstructorMetadata.versionRequirementAccept(clazz, kotlinClassKindMetadata, this); } } //Implementations for KotlinTypeParameterVisitor. @Override public void visitAnyTypeParameter(Clazz clazz, KotlinTypeParameterMetadata kotlinTypeParameterMetadata) { if (isUsed(kotlinTypeParameterMetadata)) { markAsUsed(kotlinTypeParameterMetadata.upperBounds); kotlinTypeParameterMetadata.upperBoundsAccept(clazz, this); kotlinTypeParameterMetadata.annotationsAccept(clazz, this); } } // Implementations for KotlinValueParameterVisitor. @Override public void visitAnyValueParameter(Clazz clazz, KotlinValueParameterMetadata kotlinValueParameterMetadata) {} @Override public void visitFunctionValParameter(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata, KotlinValueParameterMetadata kotlinValueParameterMetadata) { if (isUsed(kotlinValueParameterMetadata)) { kotlinValueParameterMetadata.typeAccept(clazz, kotlinMetadata, kotlinFunctionMetadata, this); } } @Override public void visitConstructorValParameter(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata, KotlinConstructorMetadata kotlinConstructorMetadata, KotlinValueParameterMetadata kotlinValueParameterMetadata) { if (isUsed(kotlinValueParameterMetadata)) { kotlinValueParameterMetadata.typeAccept(clazz, kotlinClassKindMetadata, kotlinConstructorMetadata, this); } } @Override public void visitPropertyValParameter(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata, KotlinValueParameterMetadata kotlinValueParameterMetadata) { if (isUsed(kotlinValueParameterMetadata)) { kotlinValueParameterMetadata.typeAccept(clazz, kotlinDeclarationContainerMetadata, kotlinPropertyMetadata, this); } } // Implementations for KotlinVersionRequirementVisitor @Override public void visitAnyVersionRequirement(Clazz clazz, KotlinVersionRequirementMetadata kotlinVersionRequirementMetadata) { markAsUsed(kotlinVersionRequirementMetadata); } // Implementations for KotlinAnnotationVisitor. @Override public void visitAnyAnnotation(Clazz clazz, KotlinAnnotatable annotatable, KotlinAnnotation annotation) { if (!isUsed(annotation)) { if (isJavaClassUsed(annotation.referencedAnnotationClass)) { markAsUsed(annotation); } } } // Implementations for KotlinContractVisitor. @Override public void visitContract(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata, KotlinContractMetadata kotlinContractMetadata) { if (isUsed(kotlinContractMetadata)) { markAsUsed(kotlinContractMetadata.effects); kotlinContractMetadata.effectsAccept(clazz, kotlinMetadata, kotlinFunctionMetadata, this); } } // Implementations for KotlinEffectVisitor. @Override public void visitEffect(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata, KotlinContractMetadata kotlinContractMetadata, KotlinEffectMetadata kotlinEffectMetadata) { if (isUsed(kotlinEffectMetadata)) { markAsUsed(kotlinEffectMetadata.constructorArguments); markAsUsed(kotlinEffectMetadata.conclusionOfConditionalEffect); kotlinEffectMetadata.constructorArgumentAccept(clazz, this); kotlinEffectMetadata.conclusionOfConditionalEffectAccept(clazz, this); } } // Implementations for KotlinEffectExpressionVisitor. @Override public void visitAnyEffectExpression(Clazz clazz, KotlinEffectMetadata kotlinEffectMetadata, KotlinEffectExpressionMetadata kotlinEffectExpressionMetadata) { if (!isUsed(kotlinEffectExpressionMetadata)) { markAsUsed(kotlinEffectExpressionMetadata.typeOfIs); markAsUsed(kotlinEffectExpressionMetadata.orRightHandSides); markAsUsed(kotlinEffectExpressionMetadata.andRightHandSides); kotlinEffectExpressionMetadata.typeOfIsAccept(clazz, this); kotlinEffectExpressionMetadata.orRightHandSideAccept( clazz, kotlinEffectMetadata, this); kotlinEffectExpressionMetadata.andRightHandSideAccept(clazz, kotlinEffectMetadata, this); } } // Small helper methods. private void markAsUsed(Processable metadataElement) { if (metadataElement != null) { ClassUsageMarker.this.markAsUsed(metadataElement); } } private boolean isJavaClassUsed(Clazz clazz) { // Because Kotlin dummy classes (see ClassReferenceInitializer) won't be marked as used // we must also check the DONT_SHRINK flag. return ClassUsageMarker.this.isUsed(clazz) || ((clazz.getProcessingFlags() & ProcessingFlags.DONT_SHRINK) != 0); } private void markAsUsed(List metadataElements) { metadataElements.forEach(this::markAsUsed); } } } ================================================ FILE: base/src/main/java/proguard/shrink/InnerUsageMarker.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.ClassVisitor; /** * This AttributeVisitor recursively marks all necessary inner class information * in the attributes that it visits. * * @see ClassUsageMarker * * @author Eric Lafortune */ public class InnerUsageMarker implements AttributeVisitor, InnerClassesInfoVisitor, ConstantVisitor, ClassVisitor { private final ClassUsageMarker classUsageMarker; // Fields acting as return parameters for several methods. private boolean attributeUsed; private boolean classUsed; /** * Creates a new InnerUsageMarker. * @param classUsageMarker the marker to mark and check the classes and * class members. */ public InnerUsageMarker(ClassUsageMarker classUsageMarker) { this.classUsageMarker = classUsageMarker; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { // Mark the necessary inner classes information. attributeUsed = false; innerClassesAttribute.innerClassEntriesAccept(clazz, this); if (attributeUsed) { // We got a positive used flag, so some inner class is being used. // Mark this attribute as being used as well. classUsageMarker.markAsUsed(innerClassesAttribute); markConstant(clazz, innerClassesAttribute.u2attributeNameIndex); } } // Implementations for InnerClassesInfoVisitor. public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { boolean innerClassesInfoUsed = classUsageMarker.isUsed(innerClassesInfo); if (!innerClassesInfoUsed) { // Check if the inner class (if any) is marked as being used. classUsed = true; innerClassesInfo.innerClassConstantAccept(clazz, this); innerClassesInfoUsed = classUsed; // Check if the outer class (if any) is marked as being used. classUsed = true; innerClassesInfo.outerClassConstantAccept(clazz, this); innerClassesInfoUsed &= classUsed; // If both the inner class and the outer class are marked as being // used, then mark this InnerClassesInfo as well. if (innerClassesInfoUsed) { classUsageMarker.markAsUsed(innerClassesInfo); innerClassesInfo.innerNameConstantAccept(clazz, this); } } // The return value. attributeUsed |= innerClassesInfoUsed; } // Implementations for ConstantVisitor. public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { classUsed = classUsageMarker.isUsed(classConstant); // Is the class constant marked as being used? if (!classUsed) { // Check the referenced class. classUsed = true; classConstant.referencedClassAccept(this); // Is the referenced class marked as being used? if (classUsed) { // Mark the class constant and its Utf8 constant. classUsageMarker.markAsUsed(classConstant); markConstant(clazz, classConstant.u2nameIndex); } } } public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { classUsageMarker.markAsUsed(utf8Constant); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { classUsed = classUsageMarker.isUsed(programClass); } @Override public void visitLibraryClass(LibraryClass libraryClass) { classUsed = true; } // Small utility methods. /** * Marks the given constant pool entry of the given class. This includes * visiting any other referenced constant pool entries. */ private void markConstant(Clazz clazz, int index) { clazz.constantPoolEntryAccept(index, this); } } ================================================ FILE: base/src/main/java/proguard/shrink/InterfaceUsageMarker.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor recursively marks all interface * classes that are being used in the visited class. * * @see ClassUsageMarker * * @author Eric Lafortune */ public class InterfaceUsageMarker implements ClassVisitor, ConstantVisitor { private final ClassUsageMarker classUsageMarker; // Fields acting as return parameters for the visitor methods. private boolean used; private boolean anyUsed; /** * Creates a new InterfaceUsageMarker. * @param classUsageMarker the marker to mark and check the classes and * class members. */ public InterfaceUsageMarker(ClassUsageMarker classUsageMarker) { this.classUsageMarker = classUsageMarker; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { boolean classUsed = classUsageMarker.isUsed(programClass); boolean classPossiblyUsed = classUsageMarker.isPossiblyUsed(programClass); if (classUsed || classPossiblyUsed) { // Check if any interfaces are being used. boolean oldAnyUsed = anyUsed; anyUsed = false; programClass.interfaceConstantsAccept(this); classUsed |= anyUsed; anyUsed = oldAnyUsed; // Is this an interface with a preliminary mark? if (classPossiblyUsed) { // Should it be included now? if (classUsed) { // At least one if this interface's interfaces is being used. // Mark this interface as well. classUsageMarker.markAsUsed(programClass); // Mark this interface's name. programClass.thisClassConstantAccept(this); // Mark the superclass (java/lang/Object). programClass.superClassConstantAccept(this); } else { // Unmark this interface, so we don't bother looking at it again. classUsageMarker.markAsUnused(programClass); } } } // The return value. used = classUsed; } @Override public void visitLibraryClass(LibraryClass libraryClass) { // The return values. used = true; anyUsed = true; } // Implementations for ConstantVisitor. public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { boolean classUsed = classUsageMarker.isUsed(classConstant); if (!classUsed) { // The ClassConstant isn't marked as being used yet. But maybe it // should be included as an interface, so check the actual class. classConstant.referencedClassAccept(this); classUsed = used; if (classUsed) { // The class is being used. Mark the ClassConstant as being used // as well. classUsageMarker.markAsUsed(classConstant); clazz.constantPoolEntryAccept(classConstant.u2nameIndex, this); } } // The return values. used = classUsed; anyUsed |= classUsed; } public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { if (!classUsageMarker.isUsed(utf8Constant)) { classUsageMarker.markAsUsed(utf8Constant); } } } ================================================ FILE: base/src/main/java/proguard/shrink/KotlinModuleShrinker.java ================================================ /* * 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.shrink; import proguard.classfile.kotlin.*; import proguard.resources.kotlinmodule.*; import proguard.resources.kotlinmodule.visitor.KotlinModulePackageVisitor; import proguard.resources.file.ResourceFile; import proguard.resources.file.visitor.*; import java.util.*; /** * Shrink the contents of the Kotlin module based on if the referenced * classes are used or not. * * @author James Hamilton */ public class KotlinModuleShrinker implements ResourceFileVisitor, KotlinModulePackageVisitor { private final SimpleUsageMarker usageMarker; KotlinModuleShrinker(SimpleUsageMarker usageMarker) { this.usageMarker = usageMarker; } // Implementations for ResourceFileVisitor. @Override public void visitKotlinModule(KotlinModule kotlinModule) { kotlinModule.modulePackagesAccept(this); } // Implementations for KotlinModulePackageVisitor. @Override public void visitKotlinModulePackage(KotlinModule kotlinModule, KotlinModulePackage kotlinModulePart) { // Shrink the referenced facades. for (int k = kotlinModulePart.referencedFileFacades.size() - 1; k >= 0; k--) { KotlinFileFacadeKindMetadata kotlinFileFacadeKindMetadata = kotlinModulePart.referencedFileFacades.get(k); if (!usageMarker.isUsed(kotlinFileFacadeKindMetadata)) { kotlinModulePart.fileFacadeNames .remove(k); kotlinModulePart.referencedFileFacades.remove(k); } } // Shrink the multi-file-part -> facade map. List partsToRemove = new ArrayList<>(); Set facadesToRemove = new HashSet<>(); kotlinModulePart.multiFileClassParts.forEach((partName, facadeName) -> { KotlinMultiFilePartKindMetadata referencedMultiFilePart = kotlinModulePart.referencedMultiFileParts.get(partName); if (!usageMarker.isUsed(referencedMultiFilePart.ownerReferencedClass)) { partsToRemove.add(partName); } if (!usageMarker.isUsed(referencedMultiFilePart.referencedFacadeClass)) { facadesToRemove.add(facadeName); } }); for (String partName : partsToRemove) { kotlinModulePart.multiFileClassParts .remove(partName); kotlinModulePart.referencedMultiFileParts.remove(partName); } for (String facadeName : facadesToRemove) { kotlinModulePart.multiFileClassParts.values().removeIf(f -> f.equals(facadeName)); } } } ================================================ FILE: base/src/main/java/proguard/shrink/KotlinModuleUsageMarker.java ================================================ /* * 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.shrink; import proguard.resources.kotlinmodule.*; import proguard.resources.kotlinmodule.visitor.KotlinModulePackageVisitor; import proguard.resources.file.ResourceFile; import proguard.resources.file.visitor.*; /** * Mark Kotlin modules with the given {@link SimpleUsageMarker} if required. * * @author James Hamilton */ public class KotlinModuleUsageMarker implements ResourceFileVisitor, KotlinModulePackageVisitor { private final SimpleUsageMarker usageMarker; public KotlinModuleUsageMarker(SimpleUsageMarker usageMarker) { this.usageMarker = usageMarker; } private boolean isUsed = false; // Implementations for ResourceFileVisitor. @Override public void visitKotlinModule(KotlinModule kotlinModule) { isUsed = false; kotlinModule.modulePackagesAccept(this); if (isUsed) { usageMarker.markAsUsed(kotlinModule); } } // Implementations for KotlinModulePackageVisitor. @Override public void visitKotlinModulePackage(KotlinModule kotlinModule, KotlinModulePackage kotlinModulePart) { // Mark the module as used if there are any file facades or multi-file class parts that are used. isUsed |= kotlinModulePart.referencedFileFacades.stream() .anyMatch(usageMarker::isUsed) || kotlinModulePart.referencedMultiFileParts.values().stream() .anyMatch(mfp -> usageMarker.isUsed(mfp.referencedFacadeClass)); } } ================================================ FILE: base/src/main/java/proguard/shrink/KotlinShrinker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.shrink; import proguard.classfile.AccessConstants; import proguard.classfile.Clazz; import proguard.classfile.kotlin.KotlinClassKindMetadata; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.kotlin.KotlinConstructorMetadata; import proguard.classfile.kotlin.KotlinDeclarationContainerMetadata; import proguard.classfile.kotlin.KotlinFileFacadeKindMetadata; import proguard.classfile.kotlin.KotlinFunctionMetadata; import proguard.classfile.kotlin.KotlinMetadata; import proguard.classfile.kotlin.KotlinMultiFileFacadeKindMetadata; import proguard.classfile.kotlin.KotlinMultiFilePartKindMetadata; import proguard.classfile.kotlin.KotlinPropertyMetadata; import proguard.classfile.kotlin.KotlinSyntheticClassKindMetadata; import proguard.classfile.kotlin.KotlinTypeAliasMetadata; import proguard.classfile.kotlin.KotlinTypeMetadata; import proguard.classfile.kotlin.KotlinTypeParameterMetadata; import proguard.classfile.kotlin.KotlinValueParameterMetadata; import proguard.classfile.kotlin.KotlinVersionRequirementMetadata; import proguard.classfile.kotlin.visitor.AllTypeVisitor; import proguard.classfile.kotlin.visitor.KotlinConstructorVisitor; import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; import proguard.classfile.kotlin.visitor.KotlinPropertyVisitor; import proguard.classfile.kotlin.visitor.KotlinTypeAliasVisitor; import proguard.classfile.kotlin.visitor.KotlinTypeParameterVisitor; import proguard.classfile.kotlin.visitor.KotlinTypeVisitor; import proguard.classfile.kotlin.visitor.KotlinValueParameterVisitor; import proguard.classfile.kotlin.visitor.KotlinVersionRequirementVisitor; import proguard.classfile.visitor.MemberAccessFlagCleaner; import proguard.classfile.visitor.MemberAccessSetter; import proguard.classfile.visitor.MultiMemberVisitor; import proguard.util.Processable; import java.util.List; public class KotlinShrinker implements KotlinMetadataVisitor, // Implementation interfaces. KotlinPropertyVisitor, KotlinFunctionVisitor, KotlinTypeAliasVisitor, KotlinTypeVisitor, KotlinConstructorVisitor, KotlinTypeParameterVisitor, KotlinValueParameterVisitor, KotlinVersionRequirementVisitor { private final SimpleUsageMarker usageMarker; KotlinShrinker(SimpleUsageMarker usageMarker) { this.usageMarker = usageMarker; } @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} // Implementations for KotlinMetadataVisitor. @Override public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) { // Compact the metadata's lists of properties and functions. shrinkMetadataArray(kotlinDeclarationContainerMetadata.properties); shrinkMetadataArray(kotlinDeclarationContainerMetadata.functions); shrinkMetadataArray(kotlinDeclarationContainerMetadata.typeAliases); shrinkMetadataArray(kotlinDeclarationContainerMetadata.localDelegatedProperties); // Compact each remaining property and function. kotlinDeclarationContainerMetadata.propertiesAccept( clazz, this); kotlinDeclarationContainerMetadata.functionsAccept( clazz, this); kotlinDeclarationContainerMetadata.typeAliasesAccept( clazz, this); kotlinDeclarationContainerMetadata.delegatedPropertiesAccept(clazz, this); } @Override public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { // Compact the metadata's own fields. if (shouldShrinkMetadata(kotlinClassKindMetadata.companionObjectName, kotlinClassKindMetadata.referencedCompanionClass)) { kotlinClassKindMetadata.companionObjectName = null; kotlinClassKindMetadata.referencedCompanionClass = null; } shrinkMetadataArray(kotlinClassKindMetadata.constructors); shrinkArray(kotlinClassKindMetadata.enumEntryNames, kotlinClassKindMetadata.referencedEnumEntries); shrinkArray(kotlinClassKindMetadata.nestedClassNames, kotlinClassKindMetadata.referencedNestedClasses); shrinkArray(kotlinClassKindMetadata.sealedSubclassNames, kotlinClassKindMetadata.referencedSealedSubClasses); visitKotlinDeclarationContainerMetadata(clazz, kotlinClassKindMetadata); kotlinClassKindMetadata.superTypesAccept( clazz, this); kotlinClassKindMetadata.typeParametersAccept( clazz, this); kotlinClassKindMetadata.versionRequirementAccept( clazz, this); kotlinClassKindMetadata.constructorsAccept( clazz, this); kotlinClassKindMetadata.contextReceiverTypesAccept( clazz, this); kotlinClassKindMetadata.inlineClassUnderlyingPropertyTypeAccept(clazz, this); } @Override public void visitKotlinFileFacadeMetadata(Clazz clazz, KotlinFileFacadeKindMetadata kotlinFileFacadeKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinFileFacadeKindMetadata); } @Override public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassKindMetadata kotlinSyntheticClassKindMetadata) { // Compact the metadata's lists of functions. shrinkMetadataArray(kotlinSyntheticClassKindMetadata.functions); // Compact each remaining property and function. kotlinSyntheticClassKindMetadata.functionsAccept(clazz, this); } @Override public void visitKotlinMultiFileFacadeMetadata(Clazz clazz, KotlinMultiFileFacadeKindMetadata kotlinMultiFileFacadeKindMetadata) { shrinkArray(kotlinMultiFileFacadeKindMetadata.partClassNames, kotlinMultiFileFacadeKindMetadata.referencedPartClasses); } @Override public void visitKotlinMultiFilePartMetadata(Clazz clazz, KotlinMultiFilePartKindMetadata kotlinMultiFilePartKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinMultiFilePartKindMetadata); } // Implementations for KotlinPropertyVisitor. @Override public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata) { kotlinPropertyMetadata.versionRequirementAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.typeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.setterParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.receiverTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.contextReceiverTypesAccept(clazz, kotlinDeclarationContainerMetadata, this); kotlinPropertyMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); if (shouldShrinkMetadata(kotlinPropertyMetadata.backingFieldSignature, kotlinPropertyMetadata.referencedBackingField)) { kotlinPropertyMetadata.backingFieldSignature = null; kotlinPropertyMetadata.referencedBackingField = null; } if (shouldShrinkMetadata(kotlinPropertyMetadata.getterSignature, kotlinPropertyMetadata.referencedGetterMethod)) { kotlinPropertyMetadata.getterSignature = null; kotlinPropertyMetadata.referencedGetterMethod = null; kotlinPropertyMetadata.flags.hasGetter = false; } if (shouldShrinkMetadata(kotlinPropertyMetadata.setterSignature, kotlinPropertyMetadata.referencedSetterMethod)) { kotlinPropertyMetadata.setterSignature = null; kotlinPropertyMetadata.referencedSetterMethod = null; kotlinPropertyMetadata.flags.hasSetter = false; kotlinPropertyMetadata.setterParameters.clear(); } kotlinPropertyMetadata.versionRequirementAccept(clazz, kotlinDeclarationContainerMetadata, this); if (kotlinPropertyMetadata.syntheticMethodForAnnotations != null && !usageMarker.isUsed(kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations)) { kotlinPropertyMetadata.syntheticMethodForAnnotations = null; kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations = null; kotlinPropertyMetadata.referencedSyntheticMethodClass = null; kotlinPropertyMetadata.flags.common.hasAnnotations = false; } if (kotlinPropertyMetadata.syntheticMethodForDelegate != null && !usageMarker.isUsed(kotlinPropertyMetadata.referencedSyntheticMethodForDelegateMethod)) { kotlinPropertyMetadata.syntheticMethodForDelegate = null; kotlinPropertyMetadata.referencedSyntheticMethodForDelegateClass = null; kotlinPropertyMetadata.referencedSyntheticMethodForDelegateMethod = null; } // Fix inconsistencies that were introduced as // a result of shrinking if (kotlinPropertyMetadata.referencedBackingField != null && kotlinPropertyMetadata.getterSignature == null && kotlinPropertyMetadata.setterSignature == null && (kotlinPropertyMetadata.referencedBackingField.getAccessFlags() & AccessConstants.PRIVATE) != 0 && !kotlinPropertyMetadata.flags.visibility.isPrivate) { int visibility = kotlinPropertyMetadata.flags.visibility.isProtected ? AccessConstants.PROTECTED : AccessConstants.PUBLIC; kotlinPropertyMetadata.referencedBackingField.accept(kotlinPropertyMetadata.referencedBackingFieldClass, new MultiMemberVisitor( new MemberAccessFlagCleaner(AccessConstants.PRIVATE), new MemberAccessSetter(visibility))); } } // Implementations for KotlinFunctionVisitor. @Override public void visitAnyFunction(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { if (kotlinFunctionMetadata.referencedLambdaClassOrigin != null && shouldShrinkMetadata(kotlinFunctionMetadata.lambdaClassOriginName, kotlinFunctionMetadata.referencedLambdaClassOrigin)) { kotlinFunctionMetadata.lambdaClassOriginName = null; kotlinFunctionMetadata.referencedLambdaClassOrigin = null; } kotlinFunctionMetadata.receiverTypeAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.contextReceiverTypesAccept(clazz, kotlinMetadata, this); kotlinFunctionMetadata.typeParametersAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.valueParametersAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.returnTypeAccept( clazz, kotlinMetadata, this); kotlinFunctionMetadata.contractsAccept( clazz, kotlinMetadata, new AllTypeVisitor(this)); if (kotlinFunctionMetadata.referencedDefaultMethod != null && !usageMarker.isUsed(kotlinFunctionMetadata.referencedDefaultMethod)) { kotlinFunctionMetadata.referencedDefaultMethod = null; kotlinFunctionMetadata.referencedDefaultMethodClass = null; } if (kotlinFunctionMetadata.referencedDefaultImplementationMethod != null && !usageMarker.isUsed(kotlinFunctionMetadata.referencedDefaultImplementationMethod)) { kotlinFunctionMetadata.referencedDefaultImplementationMethod = null; kotlinFunctionMetadata.referencedDefaultImplementationMethodClass = null; } // Fix inconsistencies that were introduced as // a result of shrinking. if (!kotlinFunctionMetadata.flags.modality.isAbstract && kotlinMetadata.k == KotlinConstants.METADATA_KIND_CLASS && ((KotlinClassKindMetadata)kotlinMetadata).flags.isInterface && kotlinFunctionMetadata.referencedDefaultImplementationMethod == null) { kotlinFunctionMetadata.flags.modality.isAbstract = true; } } // Implementations for KotlinConstructorVisitor. @Override public void visitConstructor(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata, KotlinConstructorMetadata kotlinConstructorMetadata) { kotlinConstructorMetadata.valueParametersAccept( clazz, kotlinClassKindMetadata, this); kotlinConstructorMetadata.versionRequirementAccept(clazz, kotlinClassKindMetadata, this); } // Implementations for KotlinTypeAliasVisitor. @Override public void visitTypeAlias(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinTypeAliasMetadata kotlinTypeAliasMetadata) { kotlinTypeAliasMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinTypeAliasMetadata.underlyingTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinTypeAliasMetadata.expandedTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); kotlinTypeAliasMetadata.versionRequirementAccept(clazz, kotlinDeclarationContainerMetadata, this); shrinkMetadataArray(kotlinTypeAliasMetadata.annotations); } // Implementations for KotlinTypeVisitor. @Override public void visitAnyType(Clazz clazz, KotlinTypeMetadata kotlinTypeMetadata) { kotlinTypeMetadata.typeArgumentsAccept(clazz, this); kotlinTypeMetadata.upperBoundsAccept( clazz, this); kotlinTypeMetadata.abbreviationAccept( clazz, this); shrinkMetadataArray(kotlinTypeMetadata.annotations); } // Implementations for KotlinTypeParameterVisitor. @Override public void visitAnyTypeParameter(Clazz clazz, KotlinTypeParameterMetadata kotlinTypeParameterMetadata) { kotlinTypeParameterMetadata.upperBoundsAccept(clazz, this); shrinkMetadataArray(kotlinTypeParameterMetadata.annotations); } // Implementations for KotlinValueParameterVisitor. @Override public void visitAnyValueParameter(Clazz clazz, KotlinValueParameterMetadata kotlinValueParameterMetadata) {} @Override public void visitFunctionValParameter(Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata, KotlinValueParameterMetadata kotlinValueParameterMetadata) { kotlinValueParameterMetadata.typeAccept(clazz, kotlinMetadata, kotlinFunctionMetadata, this); if (kotlinValueParameterMetadata.flags.hasDefaultValue && !usageMarker.isUsed(kotlinFunctionMetadata.referencedDefaultMethod)) { kotlinValueParameterMetadata.flags.hasDefaultValue = false; } } @Override public void visitConstructorValParameter(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata, KotlinConstructorMetadata kotlinConstructorMetadata, KotlinValueParameterMetadata kotlinValueParameterMetadata) { kotlinValueParameterMetadata.typeAccept(clazz, kotlinClassKindMetadata, kotlinConstructorMetadata, this); } @Override public void visitPropertyValParameter(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata, KotlinValueParameterMetadata kotlinValueParameterMetadata) { kotlinValueParameterMetadata.typeAccept(clazz, kotlinDeclarationContainerMetadata, kotlinPropertyMetadata, this); } @Override public void visitAnyVersionRequirement(Clazz clazz, KotlinVersionRequirementMetadata kotlinVersionRequirementMetadata) { } // Small helper methods. /** * Returns whether the given metadata element has its corresponding jvm * implementation element shrunk, and thus should be shrunk itself. */ private boolean shouldShrinkMetadata(Object metadataElement, Processable jvmElement) { // If this method throws a NPE, there is a kotlin metadata // element for which we could not find the corresponding // jvm element to cache yet (see ClassReferenceFixer.visitKotlinMetadata). return metadataElement != null && !usageMarker.isUsed(jvmElement); } /** * Shrinks elements and their corresponding referenced element, based on * markings on the referenced element. * * List is modified - must be a modifiable list! */ private void shrinkArray(List elements, List referencedJavaElements) { shrinkArray(usageMarker, elements, referencedJavaElements); } /** * Shrinks elements and their corresponding referenced element, based on * markings on the referenced element. * * List is modified - must be a modifiable list! */ static void shrinkArray(SimpleUsageMarker usageMarker, List elements, List referencedJavaElements) { for (int k = elements.size() - 1; k >= 0; k--) { if (!usageMarker.isUsed(referencedJavaElements.get(k))) { elements .remove(k); referencedJavaElements.remove(k); } } } /** * Shrinks elements based on their markings. */ private void shrinkMetadataArray(List elements) { for (int k = elements.size() - 1; k >= 0; k--) { if (!usageMarker.isUsed(elements.get(k))) { elements.remove(k); } } } } ================================================ FILE: base/src/main/java/proguard/shrink/LocalVariableTypeUsageMarker.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.Constant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.ClassVisitor; /** * This AttributeVisitor recursively marks all information that points to used * classes, in the LocalVariableTable and LocalVariableTypeTable attributes that * it visits. * * @see ClassUsageMarker * * @author Eric Lafortune */ public class LocalVariableTypeUsageMarker implements AttributeVisitor, LocalVariableInfoVisitor, LocalVariableTypeInfoVisitor, ClassVisitor, ConstantVisitor { private final ClassUsageMarker classUsageMarker; // Fields acting as return values for several visitor methods. private boolean tableUsed; private boolean variableInfoUsed; /** * Creates a new LocalVariableTypeUsageMarker. * @param classUsageMarker the marker to mark and check the classes and * class members. */ public LocalVariableTypeUsageMarker(ClassUsageMarker classUsageMarker) { this.classUsageMarker = classUsageMarker; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { // Check and mark the individual entries. tableUsed = false; localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); // Mark the table if any of the entries is marked. if (tableUsed) { classUsageMarker.markAsUsed(localVariableTableAttribute); markConstant(clazz, localVariableTableAttribute.u2attributeNameIndex); } } public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { // Check and mark the individual entries. tableUsed = false; localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); // Mark the table if any of the entries is marked. if (tableUsed) { classUsageMarker.markAsUsed(localVariableTypeTableAttribute); markConstant(clazz, localVariableTypeTableAttribute.u2attributeNameIndex); } } // Implementations for LocalVariableInfoVisitor. public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) { // Only keep the local variable info if all of its classes are used. variableInfoUsed = true; localVariableInfo.referencedClassAccept(this); if (variableInfoUsed) { // We got a positive used flag, so the local variable info is useful. classUsageMarker.markAsUsed(localVariableInfo); markConstant(clazz, localVariableInfo.u2nameIndex); markConstant(clazz, localVariableInfo.u2descriptorIndex); tableUsed = true; } } // Implementations for LocalVariableTypeInfoVisitor. public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) { // Only keep the local variable info if all of its classes are used. variableInfoUsed = true; localVariableTypeInfo.referencedClassesAccept(this); if (variableInfoUsed) { // We got a positive used flag, so the local variable info is useful. classUsageMarker.markAsUsed(localVariableTypeInfo); markConstant(clazz, localVariableTypeInfo.u2nameIndex); markConstant(clazz, localVariableTypeInfo.u2signatureIndex); tableUsed = true; } } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { // Don't keep the local variable info if one of its classes is not used. if (!classUsageMarker.isUsed(programClass)) { variableInfoUsed = false; } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) { classUsageMarker.markAsUsed(constant); } // Small utility methods. /** * Marks the given constant pool entry of the given class. */ private void markConstant(Clazz clazz, int index) { clazz.constantPoolEntryAccept(index, this); } } ================================================ FILE: base/src/main/java/proguard/shrink/NestUsageMarker.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.ClassVisitor; /** * This AttributeVisitor marks all necessary nest host attributes, nest * members attributes, and permitted subclasses attributes that it visits. * * @see UsageMarker * * @author Eric Lafortune */ public class NestUsageMarker implements AttributeVisitor, ConstantVisitor, ClassVisitor { private final ClassUsageMarker classUsageMarker; // Fields acting as return parameters for several methods. private boolean attributeUsed; private boolean classUsed; /** * Creates a new NestUsageMarker. * @param classUsageMarker the marker to mark and check the classes and * class members. */ public NestUsageMarker(ClassUsageMarker classUsageMarker) { this.classUsageMarker = classUsageMarker; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitNestHostAttribute(Clazz clazz, NestHostAttribute nestHostAttribute) { // Mark the necessary nest host class constant. attributeUsed = false; clazz.constantPoolEntryAccept(nestHostAttribute.u2hostClassIndex, this); if (attributeUsed) { // We got a positive used flag, so the nest host class is being used. // Mark this attribute as being used as well. classUsageMarker.markAsUsed(nestHostAttribute); markConstant(clazz, nestHostAttribute.u2attributeNameIndex); } } public void visitNestMembersAttribute(Clazz clazz, NestMembersAttribute nestMembersAttribute) { // Mark the necessary nest member information. attributeUsed = false; nestMembersAttribute.memberClassConstantsAccept(clazz, this); if (attributeUsed) { // We got a positive used flag, so at least one of the nest members // is being used. Mark this attribute as being used as well. classUsageMarker.markAsUsed(nestMembersAttribute); markConstant(clazz, nestMembersAttribute.u2attributeNameIndex); } } public void visitPermittedSubclassesAttribute(Clazz clazz, PermittedSubclassesAttribute permittedSubclassesAttribute) { // Mark the necessary permitted subclasses information. attributeUsed = false; permittedSubclassesAttribute.permittedSubclassConstantsAccept(clazz, this); if (attributeUsed) { // We got a positive used flag, so at least one of the permitted // subclasses class is being used. Mark this attribute as being // used as well. classUsageMarker.markAsUsed(permittedSubclassesAttribute); markConstant(clazz, permittedSubclassesAttribute.u2attributeNameIndex); } } // Implementations for ConstantVisitor. public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { classUsed = classUsageMarker.isUsed(classConstant); // Is the class constant marked as being used? if (!classUsed) { // Check the referenced class. classUsed = true; classConstant.referencedClassAccept(this); // Is the referenced class marked as being used? if (classUsed) { // Mark the class constant and its Utf8 constant. classUsageMarker.markAsUsed(classConstant); markConstant(clazz, classConstant.u2nameIndex); } } // The return value. attributeUsed |= classUsed; } public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { classUsageMarker.markAsUsed(utf8Constant); } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { classUsed = classUsageMarker.isUsed(programClass); } @Override public void visitLibraryClass(LibraryClass libraryClass) { classUsed = true; } // Small utility methods. /** * Marks the given constant pool entry of the given class. This includes * visiting any other referenced constant pool entries. */ private void markConstant(Clazz clazz, int index) { clazz.constantPoolEntryAccept(index, this); } } ================================================ FILE: base/src/main/java/proguard/shrink/RecordComponentUsageMarker.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.visitor.*; /** * This RecordComponentInfoVisitor marks all record components that * it visits and whose corresponding fields have been marked before. * * @see ClassUsageMarker * * @author Eric Lafortune */ public class RecordComponentUsageMarker implements RecordComponentInfoVisitor, MemberVisitor, ConstantVisitor { private final ClassUsageMarker classUsageMarker; // Field acting as a return parameter. private boolean fieldUsed; /** * Creates a new InnerUsageMarker. * @param classUsageMarker the marker to mark and check the classes and * class members. */ public RecordComponentUsageMarker(ClassUsageMarker classUsageMarker) { this.classUsageMarker = classUsageMarker; } // Implementations for RecordComponentInfoVisitor. public void visitRecordComponentInfo(Clazz clazz, RecordComponentInfo recordComponentInfo) { // Is the field that corresponds to the component used? fieldUsed = false; recordComponentInfo.referencedFieldAccept(clazz, this); if (fieldUsed) { // Mark the component. classUsageMarker.markAsUsed(recordComponentInfo); // Mark its name and descriptor. markConstant(clazz, recordComponentInfo.u2nameIndex); markConstant(clazz, recordComponentInfo.u2descriptorIndex); // Mark its attributes. recordComponentInfo.attributesAccept(clazz, classUsageMarker); } } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { fieldUsed = classUsageMarker.isUsed(programField); } // Implementations for ConstantVisitor. public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { classUsageMarker.markAsUsed(utf8Constant); } // Small utility methods. /** * Marks the given constant pool entry of the given class. This includes * visiting any other referenced constant pool entries. */ private void markConstant(Clazz clazz, int index) { clazz.constantPoolEntryAccept(index, this); } } ================================================ FILE: base/src/main/java/proguard/shrink/ShortestClassUsageMarker.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.util.Processable; /** * This UsageMarker constructs the shortest chain of dependencies. * * @author Eric Lafortune * @see ClassShrinker * @see ShortestUsagePrinter */ public class ShortestClassUsageMarker extends ClassUsageMarker { /** * Creates a new ShortestUsageMarker with the given initial reason. */ public ShortestClassUsageMarker(ShortestUsageMarker usageMarker, String reason) { super(usageMarker); setCurrentUsageMark(new ShortestUsageMark(reason)); } // Overriding implementations for ClassUsageMarker. public ShortestUsageMarker getUsageMarker() { return (ShortestUsageMarker)super.getUsageMarker(); } protected void markProgramClassBody(ProgramClass programClass) { ShortestUsageMark previousUsageMark = getCurrentUsageMark(); setCurrentUsageMark(new ShortestUsageMark(getShortestUsageMark(programClass), "is extended by ", 10000, programClass)); super.markProgramClassBody(programClass); setCurrentUsageMark(previousUsageMark); } protected void markProgramFieldBody(ProgramClass programClass, ProgramField programField) { ShortestUsageMark previousUsageMark = getCurrentUsageMark(); setCurrentUsageMark(new ShortestUsageMark(getShortestUsageMark(programField), "is referenced by ", 1, programClass, programField)); super.markProgramFieldBody(programClass, programField); setCurrentUsageMark(previousUsageMark); } protected void markProgramMethodBody(ProgramClass programClass, ProgramMethod programMethod) { ShortestUsageMark previousUsageMark = getCurrentUsageMark(); setCurrentUsageMark(new ShortestUsageMark(getShortestUsageMark(programMethod), "is invoked by ", 1, programClass, programMethod)); super.markProgramMethodBody(programClass, programMethod); setCurrentUsageMark(previousUsageMark); } protected void markMethodHierarchy(Clazz clazz, Method method) { ShortestUsageMark previousUsageMark = getCurrentUsageMark(); setCurrentUsageMark(new ShortestUsageMark(getShortestUsageMark(method), "implements ", 100, clazz, method)); super.markMethodHierarchy(clazz, method); setCurrentUsageMark(previousUsageMark); } public boolean shouldBeMarkedAsUsed(ProgramClass programClass) { return getUsageMarker().shouldBeMarkedAsUsed(programClass); } public boolean shouldBeMarkedAsUsed(ProgramClass programClass, ProgramMember programMember) { return getUsageMarker().shouldBeMarkedAsUsed(programClass, programMember); } public boolean shouldBeMarkedAsUsed(Processable processable) { return getUsageMarker().shouldBeMarkedAsUsed(processable); } public boolean isUsed(Processable processable) { return getUsageMarker().isUsed(processable); } public void markAsPossiblyUsed(Processable processable) { getUsageMarker().markAsPossiblyUsed(processable); } public boolean shouldBeMarkedAsPossiblyUsed(ProgramClass programClass, ProgramMember programMember) { return getUsageMarker().shouldBeMarkedAsPossiblyUsed(programClass, programMember); } public boolean shouldBeMarkedAsPossiblyUsed(Processable processable) { return getUsageMarker().shouldBeMarkedAsPossiblyUsed(processable); } public boolean isPossiblyUsed(Processable processable) { return getUsageMarker().isPossiblyUsed(processable); } protected ShortestUsageMark getShortestUsageMark(Processable processable) { return getUsageMarker().getShortestUsageMark(processable); } // Small utility methods. private ShortestUsageMark getCurrentUsageMark() { return getUsageMarker().currentUsageMark; } private void setCurrentUsageMark(ShortestUsageMark shortestUsageMark) { getUsageMarker().setCurrentUsageMark(shortestUsageMark); } } ================================================ FILE: base/src/main/java/proguard/shrink/ShortestUsageMark.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.visitor.*; /** * This class can be used as a mark when keeping classes, class members, and * other elements. It can be certain or preliminary. It also contains additional * information about the reasons why an element is being kept. * * @see ClassShrinker * * @author Eric Lafortune */ final class ShortestUsageMark { private final boolean certain; private final String reason; private final int depth; private Clazz clazz; private Member member; /** * Creates a new certain ShortestUsageMark. * @param reason the reason for this mark. */ public ShortestUsageMark(String reason) { this.certain = true; this.reason = reason; this.depth = 0; } /** * Creates a new certain ShortestUsageMark. * @param previousUsageMark the previous mark to which this one is linked. * @param reason the reason for this mark. * @param clazz the class causing this mark. */ public ShortestUsageMark(ShortestUsageMark previousUsageMark, String reason, int cost, Clazz clazz) { this(previousUsageMark, reason, cost, clazz, null); } /** * Creates a new certain ShortestUsageMark. * @param previousUsageMark the previous mark to which this one is linked. * @param reason the reason for this mark. * @param clazz the class causing this mark. * @param member the member in the above class causing this mark. * @param cost the added cost of following this path. */ public ShortestUsageMark(ShortestUsageMark previousUsageMark, String reason, int cost, Clazz clazz, Member member) { this.certain = true; this.reason = reason; this.depth = previousUsageMark.depth + cost; this.clazz = clazz; this.member = member; } /** * Creates a new ShortestUsageMark, based on another mark. * @param otherUsageMark the other mark, whose properties will be copied. * @param certain specifies whether this is a certain mark. */ public ShortestUsageMark(ShortestUsageMark otherUsageMark, boolean certain) { this.certain = certain; this.reason = otherUsageMark.reason; this.depth = otherUsageMark.depth; this.clazz = otherUsageMark.clazz; this.member = otherUsageMark.member; } /** * Returns whether this is a certain mark. */ public boolean isCertain() { return certain; } /** * Returns the reason for this mark. */ public String getReason() { return reason; } /** * Returns whether this mark has a shorter chain of reasons than the * given mark. */ public boolean isShorter(ShortestUsageMark otherUsageMark) { return this.depth < otherUsageMark.depth; } /** * Returns whether this is mark is caused by the given class. */ public boolean isCausedBy(Clazz clazz) { return clazz.equals(this.clazz); } /** * Returns whether this is mark is caused by a mmeber of the given class. */ public boolean isCausedByMember(Clazz clazz) { return clazz.equals(this.clazz) && member != null; } /** * Returns whether this is mark is caused by the given class member. */ public boolean isCausedBy(Clazz clazz, Member member) { return clazz.equals(this.clazz) && member.equals(this.member); } /** * Applies the given class visitor to this mark's class, if any, * and if this mark doesn't have a member. */ public void acceptClassVisitor(ClassVisitor classVisitor) { if (clazz != null && member == null) { clazz.accept(classVisitor); } } /** * Applies the given class visitor to this mark's member, if any. */ public void acceptMemberVisitor(MemberVisitor memberVisitor) { if (clazz != null && member != null) { member.accept(clazz, memberVisitor); } } // Implementations for Object. public String toString() { return "certain=" + certain + ", depth="+depth+": " + reason + (clazz != null ? clazz.getName() : "(none)") + ": " + (member != null ? member.getName(clazz) : "(none)"); } } ================================================ FILE: base/src/main/java/proguard/shrink/ShortestUsageMarker.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.visitor.*; import proguard.util.Processable; /** * This SimpleUsageMarker keeps track of the shortest dependency chains. * * @author Johan Leys */ public class ShortestUsageMarker extends SimpleUsageMarker { // A field acting as a parameter to the visitor methods. public ShortestUsageMark currentUsageMark; // A utility object to check for direct or indirect references. private final MyReferenceChecker referenceChecker = new MyReferenceChecker(); public void setCurrentUsageMark(ShortestUsageMark currentUsageMark) { this.currentUsageMark = currentUsageMark; } // Implementations for SimpleUsageMarker. public void markAsUsed(Processable processable) { Object processingInfo = processable.getProcessingInfo(); ShortestUsageMark shortestUsageMark = processingInfo instanceof ShortestUsageMark && !((ShortestUsageMark)processingInfo).isCertain() && !currentUsageMark.isShorter((ShortestUsageMark)processingInfo) ? new ShortestUsageMark((ShortestUsageMark)processingInfo, true): currentUsageMark; processable.setProcessingInfo(shortestUsageMark); } public boolean isUsed(Processable processable) { Object processingInfo = processable.getProcessingInfo(); return processingInfo != null && processingInfo instanceof ShortestUsageMark && ((ShortestUsageMark)processingInfo).isCertain(); } public boolean shouldBeMarkedAsUsed(ProgramClass programClass) { Object processingInfo = programClass.getProcessingInfo(); return processingInfo == null || !(processingInfo instanceof ShortestUsageMark) || !((ShortestUsageMark)processingInfo).isCertain() || currentUsageMark.isShorter((ShortestUsageMark)processingInfo) && !referencesClassMember(currentUsageMark, programClass); } public boolean shouldBeMarkedAsUsed(ProgramClass programClass, ProgramMember programMember) { Object processingInfo = programMember.getProcessingInfo(); return processingInfo == null || !(processingInfo instanceof ShortestUsageMark) || !((ShortestUsageMark)processingInfo).isCertain() || currentUsageMark.isShorter((ShortestUsageMark)processingInfo) && !referencesClass(currentUsageMark, programClass); } public boolean shouldBeMarkedAsUsed(Processable processable) { Object processingInfo = processable.getProcessingInfo(); return processingInfo == null || !(processingInfo instanceof ShortestUsageMark) || !((ShortestUsageMark)processingInfo).isCertain() || currentUsageMark.isShorter((ShortestUsageMark)processingInfo); } public void markAsPossiblyUsed(Processable processable) { processable.setProcessingInfo(new ShortestUsageMark(currentUsageMark, false)); } public boolean shouldBeMarkedAsPossiblyUsed(ProgramClass programClass, ProgramMember programMember) { Object processingInfo = programMember.getProcessingInfo(); return processingInfo == null || !(processingInfo instanceof ShortestUsageMark) || currentUsageMark.isShorter((ShortestUsageMark)processingInfo) && // Do not overwrite a certain mark with a shorter potential mark. !((ShortestUsageMark)processingInfo).isCertain() && !referencesClass(currentUsageMark, programClass); } public boolean shouldBeMarkedAsPossiblyUsed(Processable processable) { Object processingInfo = processable.getProcessingInfo(); return processingInfo == null || !(processingInfo instanceof ShortestUsageMark) || currentUsageMark.isShorter((ShortestUsageMark)processingInfo) && // Do not overwrite a certain mark with a shorter potential mark. !((ShortestUsageMark)processingInfo).isCertain(); } public boolean isPossiblyUsed(Processable processable) { Object processingInfo = processable.getProcessingInfo(); return processingInfo != null && processingInfo instanceof ShortestUsageMark && !((ShortestUsageMark)processingInfo).isCertain(); } protected ShortestUsageMark getShortestUsageMark(Processable processable) { Object processingInfo = processable.getProcessingInfo(); return (ShortestUsageMark)processingInfo; } // Small utility methods. /** * Returns whether the given usage mark references the given class, * directly or indirectly. */ private boolean referencesClass(ShortestUsageMark shortestUsageMark, Clazz clazz) { return referenceChecker.referencesClass(shortestUsageMark, clazz); } /** * Returns whether the given usage mark references a member of the given * class, directly or indirectly. */ private boolean referencesClassMember(ShortestUsageMark shortestUsageMark, Clazz clazz) { return referenceChecker.referencesClassMember(shortestUsageMark, clazz); } /** * This class checks whether a given usage mark is caused by a given * class or a member of a given class, directly or indirectly. */ private class MyReferenceChecker implements ClassVisitor, MemberVisitor { private Clazz checkClass; private boolean checkMember; private boolean isReferencing; public boolean referencesClass(ShortestUsageMark shortestUsageMark, Clazz clazz) { checkClass = clazz; checkMember = false; isReferencing = false; checkReferenceFrom(shortestUsageMark); return isReferencing; } public boolean referencesClassMember(ShortestUsageMark shortestUsageMark, Clazz clazz) { checkClass = clazz; checkMember = true; isReferencing = false; checkReferenceFrom(shortestUsageMark); return isReferencing; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { checkReferenceFrom(programClass); } // Implementations for MemberVisitor. public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {} public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {} public void visitProgramField(ProgramClass programClass, ProgramField programField) { checkReferenceFrom(programField); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { checkReferenceFrom(programMethod); } // Small utility members. private void checkReferenceFrom(Processable processable) { // Check the causing class or member, if still necessary. if (!isReferencing) { checkReferenceFrom(getShortestUsageMark(processable)); } } private void checkReferenceFrom(ShortestUsageMark shortestUsageMark) { // Check whether the class is marked because of a member of the // class, or the class member is marked because of the class. isReferencing = checkMember ? shortestUsageMark.isCausedByMember(checkClass) : shortestUsageMark.isCausedBy(checkClass); shortestUsageMark.acceptClassVisitor(this); shortestUsageMark.acceptMemberVisitor(this); } } } ================================================ FILE: base/src/main/java/proguard/shrink/ShortestUsagePrinter.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.util.Processable; import java.io.PrintWriter; /** * This ClassVisitor and MemberVisitor prints out the reasons why * classes and class members have been marked as being used. * * @see UsageMarker * * @author Eric Lafortune */ public class ShortestUsagePrinter implements ClassVisitor, MemberVisitor, AttributeVisitor { private final ShortestUsageMarker shortestUsageMarker; private final boolean verbose; private final PrintWriter pw; /** * Creates a new UsagePrinter that prints to the given stream. * @param shortestUsageMarker the usage marker that was used to mark the * classes and class members. * @param verbose specifies whether the output should be verbose. * @param printWriter the writer to which to print. */ public ShortestUsagePrinter(ShortestUsageMarker shortestUsageMarker, boolean verbose, PrintWriter printWriter) { this.shortestUsageMarker = shortestUsageMarker; this.verbose = verbose; this.pw = printWriter; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { throw new UnsupportedOperationException(this.getClass().getName() + " does not support " + clazz.getClass().getName()); } @Override public void visitProgramClass(ProgramClass programClass) { // Print the name of this class. pw.println(ClassUtil.externalClassName(programClass.getName())); // Print the reason for keeping this class. printReason(programClass); } @Override public void visitLibraryClass(LibraryClass libraryClass) { // Print the name of this class. pw.println(ClassUtil.externalClassName(libraryClass.getName())); // Print the reason for keeping this class. pw.println(" is a library class.\n"); } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Print the name of this field. String name = programField.getName(programClass); String type = programField.getDescriptor(programClass); pw.println(ClassUtil.externalClassName(programClass.getName()) + (verbose ? ": " + ClassUtil.externalFullFieldDescription(0, name, type): "." + name)); // Print the reason for keeping this method. printReason(programField); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Print the name of this method. String name = programMethod.getName(programClass); String type = programMethod.getDescriptor(programClass); pw.print(ClassUtil.externalClassName(programClass.getName()) + (verbose ? ": " + ClassUtil.externalFullMethodDescription(programClass.getName(), 0, name, type): "." + name)); programMethod.attributesAccept(programClass, this); pw.println(); // Print the reason for keeping this method. printReason(programMethod); } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { // Print the name of this field. String name = libraryField.getName(libraryClass); String type = libraryField.getDescriptor(libraryClass); pw.println(ClassUtil.externalClassName(libraryClass.getName()) + (verbose ? ": " + ClassUtil.externalFullFieldDescription(0, name, type): "." + name)); // Print the reason for keeping this field. pw.println(" is a library field.\n"); } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { // Print the name of this method. String name = libraryMethod.getName(libraryClass); String type = libraryMethod.getDescriptor(libraryClass); pw.println(ClassUtil.externalClassName(libraryClass.getName()) + (verbose ? ": " + ClassUtil.externalFullMethodDescription(libraryClass.getName(), 0, name, type): "." + name)); // Print the reason for keeping this method. pw.println(" is a library method.\n"); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttribute.attributesAccept(clazz, method, this); } public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) { pw.print(" (" + lineNumberTableAttribute.getLowestLineNumber() + ":" + lineNumberTableAttribute.getHighestLineNumber() + ")"); } // Small utility methods. private void printReason(Processable processable) { if (shortestUsageMarker.isUsed(processable)) { ShortestUsageMark shortestUsageMark = shortestUsageMarker.getShortestUsageMark(processable); // Print the reason for keeping this class. pw.print(" " + shortestUsageMark.getReason()); // Print the class or method that is responsible, with its reasons. shortestUsageMark.acceptClassVisitor(this); shortestUsageMark.acceptMemberVisitor(this); } else { pw.println(" is not being kept.\n"); } } } ================================================ FILE: base/src/main/java/proguard/shrink/Shrinker.java ================================================ /* * 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.shrink; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.*; import proguard.classfile.*; import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor; import proguard.classfile.util.WarningLogger; import proguard.fixer.kotlin.KotlinAnnotationFlagFixer; import proguard.resources.file.visitor.ResourceFileProcessingFlagFilter; import proguard.classfile.visitor.*; import proguard.pass.Pass; import proguard.util.*; import java.io.*; /** * This pass shrinks class pools according to a given configuration. * * @author Eric Lafortune */ public class Shrinker implements Pass { private static final Logger logger = LogManager.getLogger(Shrinker.class); private final Configuration configuration; private final boolean afterOptimizer; public Shrinker(Configuration configuration, boolean afterOptimizer) { this.configuration = configuration; this.afterOptimizer = afterOptimizer; } /** * Performs shrinking of the given program class pool. */ @Override public void execute(AppView appView) throws IOException { logger.info("Shrinking..."); // We'll print out some explanation, if requested. if (configuration.whyAreYouKeeping != null && !afterOptimizer) { logger.info("Explaining why classes and class members are being kept..."); } // We'll print out the usage, if requested. if (configuration.printUsage != null && !afterOptimizer) { logger.info("Printing usage to [" + PrintWriterUtil.fileName(configuration.printUsage) + "]..."); } // Check if we have at least some keep commands. if (configuration.keep == null) { throw new IOException("You have to specify '-keep' options for the shrinking step."); } // We're using the system's default character encoding for writing to // the standard output. PrintWriter out = new PrintWriter(System.out, true); // Clean up any old processing info. appView.programClassPool.classesAccept(new ClassCleaner()); appView.libraryClassPool.classesAccept(new ClassCleaner()); // Create a visitor for marking the seeds. SimpleUsageMarker simpleUsageMarker = configuration.whyAreYouKeeping == null || afterOptimizer ? new SimpleUsageMarker() : new ShortestUsageMarker(); // Create a usage marker for resources and code, tracing the reasons // if specified. ClassUsageMarker classUsageMarker = configuration.whyAreYouKeeping == null || afterOptimizer ? new ClassUsageMarker(simpleUsageMarker) : new ShortestClassUsageMarker((ShortestUsageMarker) simpleUsageMarker, "is kept by a directive in the configuration.\n\n"); // Mark all used code and resources and resource files. new UsageMarker(configuration).mark(appView.programClassPool, appView.libraryClassPool, appView.resourceFilePool, simpleUsageMarker, classUsageMarker); // Should we explain ourselves? if (configuration.whyAreYouKeeping != null && !afterOptimizer) { // Create a visitor for explaining classes and class members. ShortestUsagePrinter shortestUsagePrinter = new ShortestUsagePrinter((ShortestUsageMarker)classUsageMarker.getUsageMarker(), configuration.verbose, out); ClassPoolVisitor whyClassPoolvisitor = new ClassSpecificationVisitorFactory() .createClassPoolVisitor(configuration.whyAreYouKeeping, shortestUsagePrinter, shortestUsagePrinter); // Mark the seeds. appView.programClassPool.accept(whyClassPoolvisitor); appView.libraryClassPool.accept(whyClassPoolvisitor); } if (configuration.printUsage != null && !afterOptimizer) { PrintWriter usageWriter = PrintWriterUtil.createPrintWriterOut(configuration.printUsage); try { // Print out items that will be removed. appView.programClassPool.classesAcceptAlphabetically( new UsagePrinter(simpleUsageMarker, true, usageWriter)); } finally { PrintWriterUtil.closePrintWriter(configuration.printUsage, usageWriter); } } // Clean up used program classes and discard unused program classes. ClassPool newProgramClassPool = new ClassPool(); appView.programClassPool.classesAccept( new UsedClassFilter(simpleUsageMarker, new MultiClassVisitor( new ClassShrinker(simpleUsageMarker), new ClassPoolFiller(newProgramClassPool) ))); appView.libraryClassPool.classesAccept( new UsedClassFilter(simpleUsageMarker, new ClassShrinker(simpleUsageMarker))); if (configuration.keepKotlinMetadata) { // Clean up Kotlin metadata for unused classes/members. newProgramClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( new KotlinShrinker(simpleUsageMarker))); newProgramClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( new KotlinAnnotationFlagFixer())); // Shrink the content of the Kotlin module files. appView.resourceFilePool.resourceFilesAccept( new ResourceFileProcessingFlagFilter(0, ProcessingFlags.DONT_PROCESS_KOTLIN_MODULE, new KotlinModuleShrinker(simpleUsageMarker))); } int newProgramClassPoolSize = newProgramClassPool.size(); // Collect some statistics. ClassCounter originalClassCounter = new ClassCounter(); appView.programClassPool.classesAccept( new ClassProcessingFlagFilter(0, ProcessingFlags.INJECTED, originalClassCounter)); ClassCounter newClassCounter = new ClassCounter(); newProgramClassPool.classesAccept( new ClassProcessingFlagFilter(0, ProcessingFlags.INJECTED, newClassCounter)); logger.info("Removing unused program classes and class elements..."); logger.info(" Original number of program classes: {}", originalClassCounter.getCount()); logger.info(" Final number of program classes: {}", newClassCounter.getCount()); if (newClassCounter.getCount() != newProgramClassPoolSize) { logger.info(" Final number of program and injected classes: {}", newProgramClassPoolSize); } // Check if we have at least some output classes. if (newProgramClassPoolSize == 0 && (configuration.warn == null || !configuration.warn.isEmpty())) { if (configuration.ignoreWarnings) { logger.warn("Warning: the output jar is empty. Did you specify the proper '-keep' options?"); } else { throw new IOException("The output jar is empty. Did you specify the proper '-keep' options?"); } } appView.programClassPool.clear(); newProgramClassPool.classesAccept(new ClassPoolFiller(appView.programClassPool)); } } ================================================ FILE: base/src/main/java/proguard/shrink/SimpleUsageMarker.java ================================================ /* * 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.shrink; import proguard.util.Processable; /** * This class marks processables, in order to remember whether they are * unused, possibly used, or definitely used. */ public class SimpleUsageMarker { private final Object POSSIBLY_USED = new Object(); private final Object USED = new Object(); /** * Marks the given processable as possibly being used. */ public void markAsPossiblyUsed(Processable processable) { processable.setProcessingInfo(POSSIBLY_USED); } /** * Returns whether the given processable has been marked as possibly * being used. */ public boolean isPossiblyUsed(Processable processable) { return processable.getProcessingInfo() == POSSIBLY_USED; } /** * Marks the given processable as being used. */ public void markAsUsed(Processable processable) { processable.setProcessingInfo(USED); } /** * Clears any usage marks from the given processable. */ public void markAsUnused(Processable processable) { processable.setProcessingInfo(null); } /** * Returns whether the given processable has been marked as being used. */ public boolean isUsed(Processable processable) { return processable.getProcessingInfo() == USED; } } ================================================ FILE: base/src/main/java/proguard/shrink/UsageMarker.java ================================================ /* * 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.shrink; import proguard.Configuration; import proguard.classfile.ClassPool; import proguard.classfile.attribute.visitor.*; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor; import proguard.classfile.visitor.*; import proguard.optimize.Optimizer; import proguard.optimize.gson.*; import proguard.resources.file.*; import proguard.resources.file.visitor.*; import proguard.util.*; /** * Marks classes, resources, resource files and native libraries as being used, * starting from the given seeds (keep rules, Android Manifest). * * @author Johan Leys */ public class UsageMarker { private final Configuration configuration; /** * Creates a new UsageMarker. */ public UsageMarker(Configuration configuration) { this.configuration = configuration; } /** * Marks classes, resources, resource files and native libraries as being * used, based on the configuration. * * @param programClassPool the program class pool. * @param libraryClassPool the library class pool. * @param resourceFilePool the resource file pool. * @param simpleUsageMarker the usage marker for marking any visitor * accepters. */ public void mark(ClassPool programClassPool, ClassPool libraryClassPool, ResourceFilePool resourceFilePool, SimpleUsageMarker simpleUsageMarker) { mark(programClassPool, libraryClassPool, resourceFilePool, simpleUsageMarker, new ClassUsageMarker(simpleUsageMarker)); } /** * Marks classes, resources, resource files and native libraries as being * used, based on the configuration. * * @param programClassPool the program class pool. * @param libraryClassPool the library class pool. * @param resourceFilePool the resource file pool. * @param simpleUsageMarker the usage marker for marking any visitor * accepters. * @param classUsageMarker the usage marker for recursively marking * classes. */ public void mark(ClassPool programClassPool, ClassPool libraryClassPool, ResourceFilePool resourceFilePool, SimpleUsageMarker simpleUsageMarker, ClassUsageMarker classUsageMarker) { // Mark the seeds. libraryClassPool.classesAccept(classUsageMarker); // Mark classes that have to be kept. programClassPool.classesAccept( new MultiClassVisitor( new ClassProcessingFlagFilter(ProcessingFlags.DONT_SHRINK, 0, classUsageMarker), new AllMemberVisitor( new MemberProcessingFlagFilter(ProcessingFlags.DONT_SHRINK, 0, classUsageMarker)) )); // Mark the elements of Kotlin metadata that need to be kept. if (configuration.keepKotlinMetadata) { // Mark Kotlin things that refer to classes that have been marked as being kept now. programClassPool.classesAccept( new ReferencedKotlinMetadataVisitor( classUsageMarker)); } // Mark the inner class and annotation information that has to be kept. programClassPool.classesAccept( new UsedClassFilter(simpleUsageMarker, new AllAttributeVisitor(true, new MultiAttributeVisitor( new InnerUsageMarker(classUsageMarker), new NestUsageMarker(classUsageMarker), new AnnotationUsageMarker(classUsageMarker), new LocalVariableTypeUsageMarker(classUsageMarker) )))); // Mark interfaces that have to be kept. programClassPool.classesAccept(new InterfaceUsageMarker(classUsageMarker)); if (configuration.keepKotlinMetadata) { // Mark the used Kotlin modules. resourceFilePool.resourceFilesAccept( new ResourceFileNameFilter(KotlinConstants.MODULE.FILE_EXPRESSION, new ResourceFileProcessingFlagFilter(0, ProcessingFlags.DONT_PROCESS_KOTLIN_MODULE, new KotlinModuleUsageMarker(simpleUsageMarker)))); } // Mark the record metadata. programClassPool.classesAccept( new AllAttributeVisitor( new AllRecordComponentInfoVisitor( new RecordComponentUsageMarker(classUsageMarker)))); // Check if the Gson optimization is enabled. StringMatcher filter = configuration.optimizations != null ? new ListParser(new NameParser()).parse(configuration.optimizations) : new ConstantMatcher(true); boolean libraryGson = filter.matches(Optimizer.LIBRARY_GSON); if (configuration.optimize && libraryGson) { // Setup Gson context that represents how Gson is used in program // class pool. GsonContext gsonContext = new GsonContext(); gsonContext.setupFor(programClassPool, libraryClassPool, null); // Mark domain classes and fields that are involved in GSON library // invocations. if (gsonContext.gsonRuntimeSettings.excludeFieldsWithModifiers) { // When fields are excluded based on modifier, we have to keep all // fields. gsonContext.gsonDomainClassPool.classesAccept( new MultiClassVisitor( classUsageMarker, new AllFieldVisitor(classUsageMarker))); } else { // When fields are not excluded based on modifier, we can keep only // the fields for which we have injected (de)serialization code. gsonContext.gsonDomainClassPool.classesAccept( new OptimizedJsonFieldVisitor(classUsageMarker, classUsageMarker)); } } } } ================================================ FILE: base/src/main/java/proguard/shrink/UsagePrinter.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import java.io.PrintWriter; /** * This ClassVisitor prints out the classes and class members that have been * marked as being used (or not used). * * @see ClassUsageMarker * * @author Eric Lafortune */ public class UsagePrinter implements ClassVisitor, MemberVisitor, AttributeVisitor { private final SimpleUsageMarker usageMarker; private final boolean printUnusedItems; private final PrintWriter pw; // A field to remember the class name, if a header is needed for class members. private String className; /** * Creates a new UsagePrinter that prints to the given writer. * @param usageMarker the usage marker that was used to mark the * classes and class members. * @param printUnusedItems a flag that indicates whether only unused items * should be printed, or alternatively, only used * items. * @param printWriter the writer to which to print. */ public UsagePrinter(SimpleUsageMarker usageMarker, boolean printUnusedItems, PrintWriter printWriter) { this.usageMarker = usageMarker; this.printUnusedItems = printUnusedItems; this.pw = printWriter; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { if (usageMarker.isUsed(programClass)) { if (printUnusedItems) { className = programClass.getName(); programClass.fieldsAccept(this); programClass.methodsAccept(this); className = null; } else { pw.println(ClassUtil.externalClassName(programClass.getName())); } } else { if (printUnusedItems) { pw.println(ClassUtil.externalClassName(programClass.getName())); } } } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { if (usageMarker.isUsed(programField) ^ printUnusedItems) { printClassNameHeader(); pw.println(" " + ClassUtil.externalFullFieldDescription( programField.getAccessFlags(), programField.getName(programClass), programField.getDescriptor(programClass))); } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if (usageMarker.isUsed(programMethod) ^ printUnusedItems) { printClassNameHeader(); pw.print(" "); programMethod.attributesAccept(programClass, this); pw.println(ClassUtil.externalFullMethodDescription( programClass.getName(), programMethod.getAccessFlags(), programMethod.getName(programClass), programMethod.getDescriptor(programClass))); } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttribute.attributesAccept(clazz, method, this); } public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) { pw.print(lineNumberTableAttribute.getLowestLineNumber() + ":" + lineNumberTableAttribute.getHighestLineNumber() + ":"); } // Small utility methods. /** * Prints the class name field. The field is then cleared, so it is not * printed again. */ private void printClassNameHeader() { if (className != null) { pw.println(ClassUtil.externalClassName(className) + ":"); className = null; } } } ================================================ FILE: base/src/main/java/proguard/shrink/UsedClassFilter.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.visitor.ClassVisitor; /** * This ClassVisitor delegates all its method calls to another ClassVisitor, * depending on whether the Clazz objects are marked as used. * * @author Eric Lafortune */ public class UsedClassFilter implements ClassVisitor { private final SimpleUsageMarker simpleUsageMarker; private final ClassVisitor usedClassVisitor; private final ClassVisitor unusedClassVisitor; /** * Creates a new UsedClassFilter. * @param simpleUsageMarker the marker that can tell whether classes * have been marked. * @param usedClassVisitor the class visitor to which the visiting * used classes will be delegated. */ public UsedClassFilter(SimpleUsageMarker simpleUsageMarker, ClassVisitor usedClassVisitor) { this(simpleUsageMarker, usedClassVisitor, null); } /** * Creates a new UsedClassFilter. * @param simpleUsageMarker the marker that can tell whether classes * have been marked. * @param usedClassVisitor the class visitor to which the visiting * used classes will be delegated. * @param unusedClassVisitor the class visitor to which the visiting * unused classes will be delegated. */ public UsedClassFilter(SimpleUsageMarker simpleUsageMarker, ClassVisitor usedClassVisitor, ClassVisitor unusedClassVisitor) { this.simpleUsageMarker = simpleUsageMarker; this.usedClassVisitor = usedClassVisitor; this.unusedClassVisitor = unusedClassVisitor; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { // Is the class marked? ClassVisitor classVisitor = simpleUsageMarker.isUsed(clazz) ? usedClassVisitor : unusedClassVisitor; if (classVisitor != null) { clazz.accept(classVisitor); } } } ================================================ FILE: base/src/main/java/proguard/shrink/UsedMemberFilter.java ================================================ /* * 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.shrink; import proguard.classfile.*; import proguard.classfile.visitor.MemberVisitor; /** * This MemberVisitor delegates all its method calls to another MemberVisitor, * but only for Member objects that are marked as used. * * @see ClassUsageMarker * * @author Eric Lafortune */ public class UsedMemberFilter implements MemberVisitor { private final SimpleUsageMarker usageMarker; private final MemberVisitor usedMemberFilter; private final MemberVisitor unusedMemberVisitor; public UsedMemberFilter(ClassUsageMarker classUsageMarker, MemberVisitor usedMemberFilter) { this(classUsageMarker.getUsageMarker(), usedMemberFilter); } /** * Creates a new UsedMemberFilter. * @param usageMarker the usage marker that is used to mark the classes * and class members. * @param usedMemberFilter the member visitor to which the visiting will be * delegated. */ public UsedMemberFilter(SimpleUsageMarker usageMarker, MemberVisitor usedMemberFilter) { this(usageMarker, usedMemberFilter, null); } /** * Creates a new UsedMemberFilter. * * @param usageMarker the usage marker that is used to mark the classes * and class members. * @param usedMemberFilter the member visitor to which the visiting of used members * will be delegated. * @param unusedMemberVisitor the member visitor to which the visiting of unused members * will bedelegated. */ public UsedMemberFilter(SimpleUsageMarker usageMarker, MemberVisitor usedMemberFilter, MemberVisitor unusedMemberVisitor) { this.usageMarker = usageMarker; this.usedMemberFilter = usedMemberFilter; this.unusedMemberVisitor = unusedMemberVisitor; } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { MemberVisitor delegateVisitor = delegateVisitor(programField); if (delegateVisitor != null) { delegateVisitor.visitProgramField(programClass, programField); } } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { MemberVisitor delegateVisitor = delegateVisitor(programMethod); if (delegateVisitor != null) { delegateVisitor.visitProgramMethod(programClass, programMethod); } } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { MemberVisitor delegateVisitor = delegateVisitor(libraryField); if (delegateVisitor != null) { delegateVisitor.visitLibraryField(libraryClass, libraryField); } } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { MemberVisitor delegateVisitor = delegateVisitor(libraryMethod); if (delegateVisitor != null) { delegateVisitor.visitLibraryMethod(libraryClass, libraryMethod); } } // Small utility methods. private MemberVisitor delegateVisitor(Member member) { return usageMarker.isUsed(member) ? usedMemberFilter : unusedMemberVisitor; } } ================================================ FILE: base/src/main/java/proguard/shrink/shrink/package.html ================================================ This package contains classes to perform shrinking of class files. ================================================ FILE: base/src/main/java/proguard/strip/KotlinAnnotationStripper.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2020 Guardsquare NV */ package proguard.strip; import proguard.AppView; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.Configuration; import proguard.classfile.Clazz; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.annotation.Annotation; import proguard.classfile.attribute.annotation.RuntimeVisibleAnnotationsAttribute; import proguard.classfile.attribute.annotation.visitor.AnnotationTypeFilter; import proguard.classfile.attribute.annotation.visitor.AnnotationVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.editor.AnnotationsAttributeEditor; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.kotlin.visitor.KotlinMetadataRemover; import proguard.classfile.kotlin.visitor.filter.KotlinClassFilter; import proguard.classfile.visitor.*; import proguard.pass.Pass; import proguard.util.ProcessingFlagSetter; import proguard.util.ProcessingFlags; import static proguard.util.ProcessingFlags.*; /** * This pass aggressively strips the kotlin.Metadata annotation from classes. We only keep * the metadata for classes/members if the class isn't processed in any way. * * @author James Hamilton */ public class KotlinAnnotationStripper implements Pass { private static final Logger logger = LogManager.getLogger(KotlinAnnotationStripper.class); private final Configuration configuration; public KotlinAnnotationStripper(Configuration configuration) { this.configuration = configuration; } @Override public void execute(AppView appView) { logger.info("Removing @kotlin.Metadata annotation where not kept..."); ClassCounter originalCounter = new ClassCounter(); MemberCounter keptMemberCounter = new MemberCounter(); MyKotlinAnnotationStripper kotlinAnnotationStripper = new MyKotlinAnnotationStripper(); ClassVisitor kotlinAnnotationStripperVisitor = new MultiClassVisitor( new KotlinClassFilter( new MultiClassVisitor( originalCounter, // The member counter won't increase if there are no members kept in the class. new CounterConditionalClassVisitor(keptMemberCounter, CounterConditionalClassVisitor::isSame, // Check how many any of the members were specifically kept. new AllMemberVisitor( new MemberProcessingFlagFilter( DONT_OBFUSCATE | DONT_SHRINK | DONT_OPTIMIZE, 0, keptMemberCounter)), // Conditional visitor for when members weren't kept, we remove the annotation // if the class does not have DONT_OBFUSCATE, DONT_SHRINK, DONT_OPTIMIZE. new ClassProcessingFlagFilter( 0, DONT_OBFUSCATE | DONT_SHRINK | DONT_OPTIMIZE, kotlinAnnotationStripper)))), // Ensure that a class that still has the annotation is marked DONT_OPTIMIZE // (e.g. if originally the member was kept but class wasn't). new KotlinClassFilter(new ProcessingFlagSetter(ProcessingFlags.DONT_OPTIMIZE))); appView.programClassPool.classesAccept(kotlinAnnotationStripperVisitor); appView.libraryClassPool.classesAccept(kotlinAnnotationStripperVisitor); logger.info(" Original number of classes with @kotlin.Metadata: {}", originalCounter.getCount()); logger.info(" Final number of classes with @kotlin.Metadata: {}", (originalCounter.getCount() - kotlinAnnotationStripper.getCount())); } private static class MyKotlinAnnotationStripper implements ClassVisitor, AttributeVisitor, AnnotationVisitor { private final KotlinMetadataRemover kotlinMetadataRemover = new KotlinMetadataRemover(); private AnnotationsAttributeEditor attributesEditor; private int count = 0; public int getCount() { return count; } // Implementations for ClassVisitor. @Override public void visitAnyClass(Clazz clazz) { clazz.attributesAccept(this); } // Implementations for AttributeVisitor. @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute) { attributesEditor = new AnnotationsAttributeEditor(runtimeVisibleAnnotationsAttribute); runtimeVisibleAnnotationsAttribute.annotationsAccept( clazz, new AnnotationTypeFilter(KotlinConstants.TYPE_KOTLIN_METADATA, this)); } // Implementations for AnnotationVisitor. @Override public void visitAnnotation(Clazz clazz, Annotation annotation) { logger.debug("Removing Kotlin metadata annotation from {}", clazz.getName()); attributesEditor.deleteAnnotation(annotation); clazz.accept(kotlinMetadataRemover); count++; } } } ================================================ FILE: base/src/main/java/proguard/util/Benchmark.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard.util; public class Benchmark { private long startTime; private int elapsedTimeMs; public void start() { startTime = System.currentTimeMillis(); } public void stop() { elapsedTimeMs = (int) (System.currentTimeMillis() - startTime); } /** * Return the elapsed time in milliseconds. */ public int getElapsedTimeMs() { return elapsedTimeMs; } } ================================================ FILE: base/src/main/java/proguard/util/HashUtil.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.util; import java.nio.charset.StandardCharsets; /** * Hashing functions. * * @author Johan Leys */ public class HashUtil { // Constants for the FNV1-a hashCode algorithm. public static final int FNV_HASH_INIT = 0x811c9dc5; public static final int FNV_HASH_PRIME = 0x01000193; /** * Convience method for computing the FNV-1a hash on the UTF-8 encoded byte representation of the given String. */ public static int hashFnv1a32_UTF8(String string) { return hash(string, FNV_HASH_INIT); } /** * Convience method for computing the FNV-1a hash on the UTF-8 encoded byte representation of the given String, * using the given initial hash value. */ public static int hash(String string, int init) { return hash(string.getBytes(StandardCharsets.UTF_8), init); } /** * FNV-1a hashing function. *

* https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function */ public static int hashFnv1a32(byte[] data) { return hash(data, FNV_HASH_INIT); } /** * Computes a hash of the given bytes, using the FNV-1a hashing function, but with a custom inital hash value. */ public static int hash(byte[] data, int init) { int hash = init; for (byte b : data) { hash ^= b; hash *= FNV_HASH_PRIME; } return hash; } // This class should never be instantiated - it contains only static utility methods. private HashUtil() {} } ================================================ FILE: base/src/main/java/proguard/util/PrintWriterUtil.java ================================================ /* * 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.util; import proguard.Configuration; import java.io.*; /** * Utility code for creating PrintWriters for printing mappings etc. * @author Johan Leys */ public class PrintWriterUtil { /** * Returns a print writer for the given file, or the standard output if * the file name is empty. */ public static PrintWriter createPrintWriterOut(File outputFile) throws FileNotFoundException, UnsupportedEncodingException { return createPrintWriterOut(outputFile, false); } /** * Returns a print writer for the given file, or the standard output if * the file name is empty. */ public static PrintWriter createPrintWriterOut(File outputFile, boolean append) throws FileNotFoundException, UnsupportedEncodingException { return createPrintWriter(outputFile, new PrintWriter(System.out, true), append); } /** * Returns a print writer for the given file, or the standard output if * the file name is empty. */ public static PrintWriter createPrintWriterErr(File outputFile) throws FileNotFoundException, UnsupportedEncodingException { return createPrintWriter(outputFile, new PrintWriter(System.err, true)); } /** * Returns a print writer for the given file, or the standard output if * the file name is empty. */ public static PrintWriter createPrintWriter(File outputFile, PrintWriter console) throws FileNotFoundException, UnsupportedEncodingException { return createPrintWriter(outputFile, console, false); } /** * Returns a print writer for the given file, or the standard output if * the file name is empty. */ public static PrintWriter createPrintWriter(File outputFile, PrintWriter console, boolean append) throws FileNotFoundException, UnsupportedEncodingException { return outputFile == Configuration.STD_OUT ? console : new PrintWriter( new BufferedWriter( new OutputStreamWriter( new FileOutputStream(outputFile, append), "UTF-8"))); } /** * Closes the given print writer, or flushes it if is the standard output. */ public static void closePrintWriter(File file, PrintWriter printWriter) { if (file == Configuration.STD_OUT) { printWriter.flush(); } else { printWriter.close(); } } /** * Returns the canonical file name for the given file, or "standard output" * if the file name is empty. */ public static String fileName(File file) { if (file == Configuration.STD_OUT) { return "standard output"; } else { try { return file.getCanonicalPath(); } catch (IOException ex) { return file.getPath(); } } } // Hide constructor for util class. private PrintWriterUtil() {} } ================================================ FILE: base/src/main/java/proguard/util/TimeUtil.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard.util; public class TimeUtil { public static String millisecondsToMinSecReadable(int timeMs) { int ms = timeMs % 1000; int s = ((timeMs - ms) / 1000) % 60; int m = timeMs / 60000; return String.format("%d:%d.%d (m:s.ms)", m, s, ms); } } ================================================ FILE: base/src/main/java/proguard/util/TransformedStringMatcher.java ================================================ /* * 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.util; /** * This StringMatcher delegates its tests to another given StringMatcher, * with strings that have been transformed with a given function. * * @author Eric Lafortune */ public class TransformedStringMatcher extends StringMatcher { private final StringFunction stringFunction; private final StringMatcher stringMatcher; /** * Creates a new TransformedStringMatcher. * @param stringFunction the function to transform strings. * @param stringMatcher the string matcher to test the transformed * strings. */ public TransformedStringMatcher(StringFunction stringFunction, StringMatcher stringMatcher) { this.stringFunction = stringFunction; this.stringMatcher = stringMatcher; } // Implementations for StringMatcher. @Override public boolean matches(String string) { return stringMatcher.matches(stringFunction.transform(string)); } @Override protected boolean matches(String string, int beginOffset, int endOffset) { return stringMatcher.matches(stringFunction.transform(string.substring(beginOffset, endOffset))); } } ================================================ FILE: base/src/main/java/proguard/util/kotlin/KotlinUnsupportedVersionChecker.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.util.kotlin; import proguard.*; import proguard.classfile.Clazz; import proguard.classfile.kotlin.*; import proguard.classfile.kotlin.visitor.*; import proguard.classfile.util.kotlin.KotlinMetadataInitializer; import proguard.classfile.visitor.ClassVisitor; import proguard.pass.Pass; import static proguard.classfile.util.kotlin.KotlinMetadataInitializer.MAX_SUPPORTED_VERSION; import static proguard.classfile.util.kotlin.KotlinMetadataInitializer.isSupportedMetadataVersion; /** * This pass checks the attached a class' attached Kotlin metadata for unsupported metadata. * Unsupported metadata may be attached to a class if {@link KotlinMetadataInitializer} could * not initialize the metadata model. *

* An exception is thrown if the cause is that the {@link KotlinMetadataInitializer} does not * support the specific version; otherwise, the metadata is simply removed. * * @author James Hamilton */ public class KotlinUnsupportedVersionChecker implements Pass { @Override public void execute(AppView appView) throws Exception { ClassVisitor unsupportedMetadataChecker = new ReferencedKotlinMetadataVisitor( new MyUnsupportedKotlinMetadataChecker() ); appView.programClassPool.classesAccept(unsupportedMetadataChecker); appView.libraryClassPool.classesAccept(unsupportedMetadataChecker); } private static class MyUnsupportedKotlinMetadataChecker implements KotlinMetadataVisitor { @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} @Override public void visitUnsupportedKotlinMetadata(Clazz clazz, UnsupportedKotlinMetadata kotlinMetadata) { if (kotlinMetadata.mv == null || kotlinMetadata.mv.length < 3 || !isSupportedMetadataVersion(new KotlinMetadataVersion(kotlinMetadata.mv))) { throw new RuntimeException( "Unsupported Kotlin metadata version found on class '" + clazz.getName() + "'." + System.lineSeparator() + "Kotlin versions up to " + MAX_SUPPORTED_VERSION.major + "." + MAX_SUPPORTED_VERSION.minor + " are supported."); } else { // Unsupported for some other reason, just remove the metadata. clazz.accept(new KotlinMetadataRemover()); } } } } ================================================ FILE: base/src/main/java/proguard/util/kotlin/asserter/KotlinMetadataVerifier.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.util.kotlin.asserter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.Configuration; import proguard.classfile.util.WarningLogger; import proguard.pass.Pass; import proguard.resources.file.ResourceFilePool; /** * This pass performs a series of checks to see whether the kotlin metadata is intact. */ public class KotlinMetadataVerifier implements Pass { private static final Logger logger = LogManager.getLogger(KotlinMetadataVerifier.class); private final Configuration configuration; public KotlinMetadataVerifier(Configuration configuration) { this.configuration = configuration; } @Override public void execute(AppView appView) { WarningLogger warningLogger = new WarningLogger(logger, configuration.warn); KotlinMetadataAsserter kotlinMetadataAsserter = new KotlinMetadataAsserter(); kotlinMetadataAsserter.execute( warningLogger, appView.programClassPool, appView.libraryClassPool, appView.resourceFilePool ); int warningCount = warningLogger.getWarningCount(); if (warningCount > 0) { logger.warn("Warning: there were {} errors during Kotlin metadata verification.", warningCount); } } } ================================================ FILE: base/src/main/java/proguard/util/kotlin/asserter/Reporter.java ================================================ /* * 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.util.kotlin.asserter; /** * Interface for reporting errors. */ public interface Reporter { void setErrorMessage(String message); void report(String error); void resetCounter(String contextName); int getCount(); void print(String className, String s); } ================================================ FILE: base/src/main/resources/log4j2.xml ================================================ ================================================ FILE: base/src/test/kotlin/proguard/AfterInitConfigurationVerifierTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard import io.kotest.assertions.throwables.shouldNotThrow import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.string.shouldContain import proguard.classfile.ProgramClass import proguard.classfile.VersionConstants.CLASS_VERSION_11 import proguard.classfile.VersionConstants.CLASS_VERSION_12 import proguard.classfile.VersionConstants.CLASS_VERSION_17 import proguard.classfile.VersionConstants.CLASS_VERSION_18 import proguard.classfile.VersionConstants.CLASS_VERSION_1_6 import testutils.asConfiguration import testutils.getLogOutputOf import java.util.UUID /** * Test the after init checks. */ class AfterInitConfigurationVerifierTest : FreeSpec({ // Mock a program class with the given class file version. class FakeClass(version: Int) : ProgramClass(version, 1, emptyArray(), 1, -1, -1) { override fun getClassName(constantIndex: Int): String { return "Fake${UUID.randomUUID()}" } } "Given a configuration with target specified" - { val configuration = """ -verbose -target 6 """.trimIndent().asConfiguration() "It should throw an exception if program class pool contains a class with class file version > jdk 11" { val view = AppView() view.programClassPool.addClass(FakeClass(CLASS_VERSION_12)) val exception = shouldThrow { AfterInitConfigurationVerifier(configuration).execute(view) } exception.message shouldContain "-target can only be used with class file versions <= 55 (Java 11)." exception.message shouldContain "The input classes contain version 56 class files which cannot be backported to target version (50)." } "It should not throw an exception if program class pool contains classes with class file version = jdk 11" { val view = AppView() view.programClassPool.addClass(FakeClass(CLASS_VERSION_11)) shouldNotThrow { AfterInitConfigurationVerifier(configuration).execute(view) } } "It should not throw an exception if program class pool contains classes with class file version < jdk 11" { val view = AppView() view.programClassPool.addClass(FakeClass(CLASS_VERSION_1_6)) shouldNotThrow { AfterInitConfigurationVerifier(configuration).execute(view) } } } "Given a configuration with a target specified and classes above Java 11" - { val configuration = """ -target 17 """.trimIndent().asConfiguration() "It should not throw an exception if -target is set but all the classes are already at the targeted version (no backport needed)" { val view = AppView() with(view.programClassPool) { addClass(FakeClass(CLASS_VERSION_17)) addClass(FakeClass(CLASS_VERSION_17)) } shouldNotThrow { AfterInitConfigurationVerifier(configuration).execute(view) } } "It should print a warning" { val view = AppView() with(view.programClassPool) { addClass(FakeClass(CLASS_VERSION_17)) addClass(FakeClass(CLASS_VERSION_17)) } val output = getLogOutputOf { AfterInitConfigurationVerifier(configuration).execute(view) } output shouldContain "-target is deprecated when using class file above" } "It should throw an exception if -target is set and the version of one of the classes version is different from the target version" { val view = AppView() with(view.programClassPool) { addClass(FakeClass(CLASS_VERSION_18)) addClass(FakeClass(CLASS_VERSION_17)) } val exception = shouldThrow { AfterInitConfigurationVerifier(configuration).execute(view) } exception.message shouldContain "-target can only be used with class file versions <= 55 (Java 11)." exception.message shouldContain "The input classes contain version 62 class files which cannot be backported to target version (61)." } } "Given a configuration with no target specified" - { val configuration = """ -verbose """.trimIndent().asConfiguration() "It should not throw an exception if program class pool contains classes with class file version > jdk 11" { val view = AppView() view.programClassPool.addClass(FakeClass(CLASS_VERSION_12)) shouldNotThrow { AfterInitConfigurationVerifier(configuration).execute(view) } } "It should not throw an exception if program class pool contains classes with class file version = jdk 11" { val view = AppView() view.programClassPool.addClass(FakeClass(CLASS_VERSION_11)) shouldNotThrow { AfterInitConfigurationVerifier(configuration).execute(view) } } "It should not throw an exception if program class pool contains classes with class file version < jdk 11" { val view = AppView() view.programClassPool.addClass(FakeClass(CLASS_VERSION_1_6)) shouldNotThrow { AfterInitConfigurationVerifier(configuration).execute(view) } } } }) ================================================ FILE: base/src/test/kotlin/proguard/ClassMergerTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.optimize.peephole import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import proguard.classfile.ProgramClass import proguard.classfile.VersionConstants.CLASS_VERSION_1_8 import proguard.classfile.visitor.ClassVersionSetter import proguard.classfile.visitor.ProcessingInfoSetter import proguard.optimize.info.ClassOptimizationInfo import proguard.optimize.info.ProgramClassOptimizationInfo import proguard.testutils.AssemblerSource import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource class ClassMergerTest : FreeSpec({ val classBPools = ClassPoolBuilder.fromSource( JavaSource( "B.java", """ public class B {} """.trimIndent() ) ) val classB = classBPools.programClassPool.getClass("B") as ProgramClass classB.accept(ClassVersionSetter(CLASS_VERSION_1_8)) classBPools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo())) classBPools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo())) "Given a non-nested class" - { val classAPools = ClassPoolBuilder.fromSource( AssemblerSource( "A.jbc", """ version 1.8; public class A extends java.lang.Object [ SourceFile "A.java"; ] { } """.trimIndent() ) ) val classA = classAPools.programClassPool.getClass("A") as ProgramClass "the check should indicate so" { ClassMerger.isNestHostOrMember(classA) shouldBe false } "it should be mergeable" { classAPools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo())) classAPools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo())) ClassMerger(classB, true, true, true).isMergeable(classA) shouldBe true } } "Given a nested class" - { val classAPools = ClassPoolBuilder.fromSource( AssemblerSource( "A.jbc", """ version 1.8; public class A extends java.lang.Object [ NestHost java.lang.Class; SourceFile "A.java"; ] { } """.trimIndent() ) ) val classA = classAPools.programClassPool.getClass("A") as ProgramClass "the check should indicate so" { ClassMerger.isNestHostOrMember(classA) shouldBe true } "it should not be mergeable" { classAPools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo())) classAPools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo())) ClassMerger(classB, true, true, true).isMergeable(classA) shouldBe false } } "Given a class with no native methods" - { val targetPools = ClassPoolBuilder.fromSource( JavaSource( "Target.java", """ public class Target { private boolean value; public Target(boolean value) { this.value = value; } public void setValue(boolean value) { this.value = value; } } """.trimIndent() ) ) targetPools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo())) targetPools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo())) val target = targetPools.programClassPool.getClass("Target") as ProgramClass val merger = ClassMerger(target, true, true, true) "When merging in a class with no native methods" - { val sourcePools = ClassPoolBuilder.fromSource( JavaSource( "Source.java", """ public class Source { private String name; public String getName() { return name; } } """.trimIndent() ) ) "Then it should be possible to merge the classes" { sourcePools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo())) sourcePools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo())) merger.isMergeable(sourcePools.programClassPool.getClass("Source") as ProgramClass) shouldBe true } } "When merging in a class with native methods" - { val sourcePools = ClassPoolBuilder.fromSource( JavaSource( "Source.java", """ public class Source { private String name; private native void method(); public String getName() { return name; } } """.trimIndent() ) ) "Then it should not be possible to merge the classes" { sourcePools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo())) sourcePools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo())) merger.isMergeable(sourcePools.programClassPool.getClass("Source") as ProgramClass) shouldBe false } } } "Given a class with native methods" - { // Same as above but checking if merging in a class with native methods is still possible. val targetPools = ClassPoolBuilder.fromSource( JavaSource( "Target.java", """ public class Target { private boolean value; public static native void foo(); public Target(boolean value) { this.value = value; } public void setValue(boolean value) { this.value = value; } } """.trimIndent() ) ) targetPools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo())) targetPools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo())) val target = targetPools.programClassPool.getClass("Target") as ProgramClass val merger = ClassMerger(target, true, true, true) "When merging in a class with no native methods" - { val sourcePools = ClassPoolBuilder.fromSource( JavaSource( "Source.java", """ public class Source { private String name; public String getName() { return name; } } """.trimIndent() ) ) "Then it should be possible to merge the classes" { sourcePools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo())) sourcePools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo())) merger.isMergeable(sourcePools.programClassPool.getClass("Source") as ProgramClass) shouldBe true } } "When merging in a class with native methods" - { val sourcePools = ClassPoolBuilder.fromSource( JavaSource( "Source.java", """ public class Source { private String name; private native void method(); public String getName() { return name; } } """.trimIndent() ) ) "Then it should not be possible to merge the classes" { sourcePools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo())) sourcePools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo())) merger.isMergeable(sourcePools.programClassPool.getClass("Source") as ProgramClass) shouldBe false } } } }) ================================================ FILE: base/src/test/kotlin/proguard/ClassSpecificationVisitorFactoryTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard import io.kotest.core.spec.style.FreeSpec import io.mockk.spyk import io.mockk.verify import proguard.classfile.visitor.ClassVisitor import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource import testutils.asConfiguration class ClassSpecificationVisitorFactoryTest : FreeSpec({ "Given a class specification with an extending class" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource("Foo.java", "class Foo extends Bar3 { }"), JavaSource("Bar3.java", "class Bar3 { }") ) val spec = "-keep class F** extends Bar* { public *; }".asConfiguration().keep.first() val classVisitor = spyk() val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor( spec, classVisitor, null, null, null ) "Then the visitor should visit the sub-class" { programClassPool.accept(visitor) verify(exactly = 1) { classVisitor.visitAnyClass(programClassPool.getClass("Foo")) } } } "Given a class specification with an implementing class" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource("Foo.java", "class Foo implements Bar3 { }"), JavaSource("Bar3.java", "interface Bar3 { }") ) val spec = "-keep class F** implements Bar* { public *; }".asConfiguration().keep.first() val classVisitor = spyk() val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor( spec, classVisitor, null, null, null ) "Then the visitor should visit the implementing class" { programClassPool.accept(visitor) verify(exactly = 1) { classVisitor.visitAnyClass(programClassPool.getClass("Foo")) } } } "Given a class specification with a negation" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource("Foo.java", "class Foo implements Bar3 { }"), JavaSource("Bar3.java", "interface Bar3 { }") ) val spec = "-keep !interface B** { public *; }".asConfiguration().keep.first() val classVisitor = spyk() val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor( spec, classVisitor, null, null, null ) "Then the visitor should not visit the interface of which the name starts with 'B'" { programClassPool.accept(visitor) verify(exactly = 0) { classVisitor.visitAnyClass(programClassPool.getClass("Bar3")) } } } "Given a conditional class specification containing a wildcard" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource("FooBar.java", "class FooBar extends Bar { }"), JavaSource("Bar.java", "class Bar { }") ) val spec = "-if class * -keep class <1> { public *; }".asConfiguration().keep.first() val classVisitor = spyk() val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor( spec, classVisitor, null, null, null ) "Then the visitor should visit the matched class" { programClassPool.accept(visitor) verify(exactly = 1) { classVisitor.visitAnyClass(programClassPool.getClass("FooBar")) } } } "Given a class specification extending an annotation class" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource("Annotation.java", "@interface Annotation { }"), JavaSource("FooBar.java", "class FooBar extends Bar { }"), JavaSource("Bar.java", "@Annotation class Bar { }") ) val spec = "-keep class * extends @Annotation *".asConfiguration().keep.first() val classVisitor = spyk() val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor( spec, classVisitor, null, null, null ) "Then the visitor should visit the matched class" { programClassPool.accept(visitor) verify(exactly = 1) { classVisitor.visitAnyClass(programClassPool.getClass("FooBar")) } } } }) ================================================ FILE: base/src/test/kotlin/proguard/ConfigurationParserTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FreeSpec /** * Some simple testcases to catch special cases when parsing the Configuration. * * @author Thomas Neidhart */ class ConfigurationParserTest : FreeSpec({ fun parseConfiguration(rules: String): Configuration { val configuration = Configuration() ConfigurationParser(rules, "", null, System.getProperties()).use { it.parse(configuration) } return configuration } fun parseConfiguration(reader: WordReader): Configuration { val configuration = Configuration() ConfigurationParser(reader, System.getProperties()).use { it.parse(configuration) } return configuration } "Keep rule tests" - { "Keep rule with wildcard should be valid" { parseConfiguration("-keep class * { ; }") } "Keep rule with wildcard and public access modifier should be valid" { parseConfiguration("-keep class * { public ; }") } "Keep rule with wildcard and public + protected access modifiers should be valid" { parseConfiguration("-keep class * { public protected ; }") } "Keep rule with wildcard should be valid" { parseConfiguration("-keep class * { ; }") } "Keep rule with wildcard and public access modifier should be valid" { parseConfiguration("-keep class * { public ; }") } "Keep rule with wildcard and public + protected access modifier should be valid" { parseConfiguration("-keep class * { public protected ; }") } "Keep rule with * member wildcard and return type should be valid" { parseConfiguration("-keep class * { java.lang.String *; }") } "Keep rule with * member wildcard, return type and empty argument list should be valid" { parseConfiguration("-keep class * { int *(); }") } "Keep rule with * member wildcard, return type and non-empty argument list should be valid" { parseConfiguration("-keep class * { int *(int); }") } "Keep rule with wildcard and explicit type should throw ParseException" { shouldThrow { parseConfiguration("-keep class * { java.lang.String ; }") } } "Keep rule with wildcard and explicit argument list should throw ParseException" { shouldThrow { parseConfiguration("-keep class * { (); }") } } } }) ================================================ FILE: base/src/test/kotlin/proguard/Java19RecordPatternTest.kt ================================================ package proguard import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldNotBe import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource import testutils.RequiresJavaVersion @RequiresJavaVersion(19, 19) class Java19RecordPatternTest : FreeSpec({ "Given a class with Java record pattern" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Test.java", """ public class Test { public static void main(String[] args) { printPoint(new Point(1, 2)); } private static void printPoint(Object o) { if (o instanceof (Point(int x, int y) p)) { System.out.println(p + " = " + (x + y)); } } } record Point(int x, int y) {} """.trimIndent() ), javacArguments = listOf("--enable-preview", "--release", "19") ) "Then ProGuard should parse the class correctly" { programClassPool.getClass("Test") shouldNotBe null programClassPool.getClass("Point") shouldNotBe null } } }) ================================================ FILE: base/src/test/kotlin/proguard/Java20RecordPatternTest.kt ================================================ package proguard import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldNotBe import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource import testutils.RequiresJavaVersion @RequiresJavaVersion(20, 20) class Java20RecordPatternTest : FreeSpec({ "Given a class with Java record pattern" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Test.java", """ public class Test { public static void main(String[] args) { printPoint(new Point(1, 2)); } private static void printPoint(Object o) { if (o instanceof Point(int x, int y)) { System.out.println(x + y); } } } record Point(int x, int y) {} """.trimIndent() ), javacArguments = listOf("--enable-preview", "--release", "20") ) "Then ProGuard should parse the class correctly" { programClassPool.getClass("Test") shouldNotBe null programClassPool.getClass("Point") shouldNotBe null } } }) ================================================ FILE: base/src/test/kotlin/proguard/LoggingTest.kt ================================================ /* * ProGuardCORE -- library to process Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV * * 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 proguard import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.string.shouldContain import org.apache.logging.log4j.core.LoggerContext import org.apache.logging.log4j.core.appender.OutputStreamAppender import java.io.ByteArrayOutputStream class LoggingTest : FreeSpec({ "Given a default logger" - { // Add an appender writing to an OutputStream we can capture instead of stdout. val loggerOutput = ByteArrayOutputStream() val context = LoggerContext.getContext(false) val config = context.configuration // Grab the same config as the current appender. val layout = config.rootLogger.appenders.values.first().layout val appender = OutputStreamAppender.createAppender(layout, null, loggerOutput, "TestLogger", false, true) appender.start() config.addAppender(appender) config.rootLogger.addAppender(appender, null, null) val testLogger = context.getLogger("TestLogger") "When logging a warning" - { testLogger.warn("Hello World") val output = loggerOutput.toString() "Then the log should contain the message" { output shouldContain "Hello World" } } } }) ================================================ FILE: base/src/test/kotlin/proguard/MarkerTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import proguard.io.ExtraDataEntryNameMap import proguard.mark.Marker import proguard.resources.file.ResourceFilePool import proguard.testutils.ClassPoolBuilder import proguard.testutils.KotlinSource import proguard.util.ProcessingFlags.DONT_OBFUSCATE import proguard.util.ProcessingFlags.DONT_OPTIMIZE import proguard.util.ProcessingFlags.DONT_SHRINK import proguard.util.kotlin.asserter.KotlinMetadataVerifier import testutils.asConfiguration import testutils.shouldHaveFlag import testutils.shouldNotHaveFlag class MarkerTest : FreeSpec({ "Given a Kotlin inline class" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ @JvmInline value class Password(val s: String) // The underlying JVM method descriptor will reference the // underlying property of `Password`, rather than `Password` itself. fun login(password: Password) { println(password) } """.trimIndent() ) ) beforeEach { programClassPool.classesAccept { it.processingFlags = 0 } } "Then when using includedescriptorclasses modifier" - { val config = """ -keep,includedescriptorclasses class TestKt { ; } """.asConfiguration() "The inline class should be marked" { val marker = Marker(config) val appView = AppView(programClassPool, libraryClassPool, ResourceFilePool(), ExtraDataEntryNameMap()) marker.execute(appView) with(programClassPool.getClass("Password").processingFlags) { this shouldHaveFlag DONT_OBFUSCATE this shouldHaveFlag DONT_OPTIMIZE this shouldHaveFlag DONT_SHRINK } } } "Then when not using includedescriptorclasses modifier" - { val config = """ -keep class TestKt { ; } """.asConfiguration() "The inline class should not be marked" { val marker = Marker(config) val appView = AppView(programClassPool, libraryClassPool, ResourceFilePool(), ExtraDataEntryNameMap()) marker.execute(appView) with(programClassPool.getClass("Password").processingFlags) { this shouldNotHaveFlag DONT_OBFUSCATE this shouldNotHaveFlag DONT_OPTIMIZE this shouldNotHaveFlag DONT_SHRINK } } } } "Given a Kotlin interface with default method implementation in the interface itself" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ interface Test { fun foo() { TODO() } } """.trimIndent() ), kotlincArguments = listOf("-Xjvm-default=all") ) // Run the asserter to ensure any metadata that isn't initialized correctly is thrown away KotlinMetadataVerifier(Configuration()).execute(AppView(programClassPool, libraryClassPool)) "Then when marking" - { val config = """ -keep class Test { ; } # This option is deprecated and one should use '-keep class kotlin.Metadata' instead. # In this test we use the deprecated option, as its value is set in ProGuard.java and not in # the ConfigurationParser which is used by this unit test. -keepkotlinmetadata """.asConfiguration() val marker = Marker(config) "There should be no DefaultImpls class" { programClassPool.getClass("Test\$DefaultImpls") shouldBe null } "An exception should not be thrown" { shouldNotThrowAny { marker.execute(AppView(programClassPool, libraryClassPool)) } } } } "Given a Kotlin interface with default method implementation in compatibility mode" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ interface Test { fun foo() { TODO() } } """.trimIndent() ), kotlincArguments = listOf("-Xjvm-default=all-compatibility") ) // Run the asserter to ensure any metadata that isn't initialized correctly is thrown away KotlinMetadataVerifier(Configuration()).execute(AppView(programClassPool, libraryClassPool)) "Then when marking" - { val config = """ -keep class Test { ; } # This option is deprecated and one should use '-keep class kotlin.Metadata' instead. # In this test we use the deprecated option, as its value is set in ProGuard.java and not in # the ConfigurationParser which is used by this unit test. -keepkotlinmetadata """.asConfiguration() val marker = Marker(config) "An exception should not be thrown" { shouldNotThrowAny { marker.execute(AppView(programClassPool, libraryClassPool)) } } "There should be no DefaultImpls class" { programClassPool.getClass("Test\$DefaultImpls") shouldNotBe null } val defaultImpls = programClassPool.getClass("Test\$DefaultImpls") "The DefaultImpls class should be marked DONT_OPTIMIZE" { defaultImpls.processingFlags shouldHaveFlag DONT_OPTIMIZE } "The default method should be marked DONT_OPTIMIZE" { defaultImpls.findMethod("foo", null).processingFlags shouldHaveFlag DONT_OPTIMIZE } } } }) ================================================ FILE: base/src/test/kotlin/proguard/obfuscate/SimpleNameFactoryTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.obfuscate import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.collections.shouldNotBeIn class SimpleNameFactoryTest : StringSpec({ val reservedNames = listOf("AUX", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "CON", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "NUL", "PRN") "Should not generate WINDOWS reserved names with mixed case enabled" { val factory = SimpleNameFactory(true) // 115895 is index for generating PRN with mixed case enabled. repeat(115896) { val name = factory.nextName() name.uppercase() shouldNotBeIn reservedNames } } "Should not generate WINDOWS reserved names with mixed case disabled" { val factory = SimpleNameFactory(false) // 44213 is index for generating prn with mixed case disabled. repeat(44214) { val name = factory.nextName() name.uppercase() shouldNotBeIn reservedNames } } }) ================================================ FILE: base/src/test/kotlin/proguard/obfuscate/kotlin/KotlinCallableReferenceFixerTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.obfuscate.kotlin import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldNotContainIgnoringCase import io.mockk.spyk import io.mockk.verify import proguard.classfile.ClassPool import proguard.classfile.Clazz import proguard.classfile.constant.Constant import proguard.classfile.constant.visitor.AllConstantVisitor import proguard.classfile.constant.visitor.ConstantVisitor import proguard.classfile.editor.ClassReferenceFixer import proguard.classfile.editor.ConstantPoolShrinker import proguard.classfile.editor.MemberReferenceFixer import proguard.classfile.kotlin.KotlinClassKindMetadata import proguard.classfile.kotlin.KotlinFileFacadeKindMetadata import proguard.classfile.kotlin.KotlinMetadata import proguard.classfile.kotlin.KotlinSyntheticClassKindMetadata import proguard.classfile.kotlin.reflect.visitor.CallableReferenceInfoVisitor import proguard.classfile.kotlin.visitor.AllPropertyVisitor import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor import proguard.classfile.util.ClassRenamer import proguard.classfile.visitor.ClassNameFilter import proguard.classfile.visitor.MultiClassVisitor import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource import proguard.testutils.KotlinSource class KotlinCallableReferenceFixerTest : FreeSpec({ "Given a function callable reference" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ fun original() = "bar" fun ref() = ::original """.trimIndent() ) ) programClassPool.classesAccept( MultiClassVisitor( ClassNameFilter( "TestKt", ClassRenamer( { "Obfuscated" }, { clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) } ) ), createFixer(programClassPool, libraryClassPool) ) ) val callableRefInfoVisitor = spyk() val ownerVisitor = spyk< KotlinMetadataVisitor>() val testVisitor = createVisitor(callableRefInfoVisitor, ownerVisitor) programClassPool.classesAccept("TestKt\$ref\$1", testVisitor) "Then the callableReferenceInfo should be initialized" { verify(exactly = 1) { callableRefInfoVisitor.visitFunctionReferenceInfo( withArg { it.name shouldBe "obfuscated" it.signature shouldBe "obfuscated()Ljava/lang/String;" it.owner shouldNotBe null } ) } verify(exactly = 1) { ownerVisitor.visitKotlinFileFacadeMetadata( withArg { it.name shouldBe "Obfuscated" }, ofType(KotlinFileFacadeKindMetadata::class) ) } } "Then the callable reference class should not mention the original name" { programClassPool.classesAccept( "TestKt\$ref\$1", AllConstantVisitor( object : ConstantVisitor { override fun visitAnyConstant(clazz: Clazz, constant: Constant) { constant.toString() shouldNotContainIgnoringCase "original" } } ) ) } } "Given a non-optimized function callable reference" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ fun original() = "bar" fun ref() = ::original """.trimIndent() ), kotlincArguments = listOf("-Xno-optimized-callable-references") ) programClassPool.classesAccept( MultiClassVisitor( ClassNameFilter( "TestKt", ClassRenamer( { "Obfuscated" }, { clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) } ) ), createFixer(programClassPool, libraryClassPool) ) ) val callableRefInfoVisitor = spyk() val ownerVisitor = spyk< KotlinMetadataVisitor>() val testVisitor = createVisitor(callableRefInfoVisitor, ownerVisitor) programClassPool.classesAccept("TestKt\$ref\$1", testVisitor) "Then the callableReferenceInfo should be initialized" { verify(exactly = 1) { callableRefInfoVisitor.visitFunctionReferenceInfo( withArg { it.name shouldBe "obfuscated" it.signature shouldBe "obfuscated()Ljava/lang/String;" it.owner shouldNotBe null } ) } verify(exactly = 1) { ownerVisitor.visitKotlinFileFacadeMetadata( withArg { it.name shouldBe "Obfuscated" }, ofType(KotlinFileFacadeKindMetadata::class) ) } } "Then the callable reference class should not mention the original name" { programClassPool.classesAccept( "TestKt\$ref\$1", AllConstantVisitor( object : ConstantVisitor { override fun visitAnyConstant(clazz: Clazz, constant: Constant) { constant.toString() shouldNotContainIgnoringCase "original" } } ) ) } } "Given a property callable reference" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ class Foo { var original = "bar" } fun ref() = Foo()::original """.trimIndent() ) ) programClassPool.classesAccept( MultiClassVisitor( ClassNameFilter( "Foo", MultiClassVisitor( ClassRenamer( { "Obfuscated" }, { clazz, member -> when { member.getName(clazz) == "getOriginal" -> "getObfuscated" member.getName(clazz) == "original" -> "obfuscated" member.getName(clazz) == "setOriginal" -> "setObfuscated" else -> member.getName(clazz) } } ), ReferencedKotlinMetadataVisitor( AllPropertyVisitor { _, _, property -> if (property.name == "original") property.name = "obfuscated" } ) ) ), createFixer(programClassPool, libraryClassPool) ) ) val callableRefInfoVisitor = spyk() val ownerVisitor = spyk< KotlinMetadataVisitor>() val testVisitor = createVisitor(callableRefInfoVisitor, ownerVisitor) programClassPool.classesAccept("TestKt\$ref\$1", testVisitor) "Then the callableReferenceInfo should be initialized" { verify(exactly = 1) { callableRefInfoVisitor.visitPropertyReferenceInfo( withArg { it.name shouldBe "obfuscated" it.signature shouldBe "getObfuscated()Ljava/lang/String;" it.owner shouldNotBe null } ) } verify(exactly = 1) { ownerVisitor.visitKotlinClassMetadata( withArg { it.name shouldBe "Obfuscated" }, ofType(KotlinClassKindMetadata::class) ) } } "Then the callable reference class should not mention the original name" { programClassPool.classesAccept( "TestKt\$ref\$1", AllConstantVisitor( object : ConstantVisitor { override fun visitAnyConstant(clazz: Clazz, constant: Constant) { constant.toString() shouldNotContainIgnoringCase "original" } } ) ) } } "Given a Java method callable reference" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ val javaClassInstance = Original() fun ref() = javaClassInstance::original """.trimIndent() ), JavaSource( "Original.java", """ public class Original { public String original() { return "bar"; } } """.trimIndent() ) ) programClassPool.classesAccept( MultiClassVisitor( ClassNameFilter( "Original", ClassRenamer( { "Obfuscated" }, { clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) } ) ), createFixer(programClassPool, libraryClassPool) ) ) val callableRefInfoVisitor = spyk() val ownerVisitor = spyk< KotlinMetadataVisitor>() val testVisitor = createVisitor(callableRefInfoVisitor, ownerVisitor) programClassPool.classesAccept("TestKt\$ref\$1", testVisitor) "Then the callableReferenceInfo should be initialized" { verify(exactly = 1) { callableRefInfoVisitor.visitJavaReferenceInfo( withArg { it.name shouldBe "obfuscated" it.signature shouldBe "obfuscated()Ljava/lang/String;" it.owner shouldBe null } ) } verify(exactly = 0) { ownerVisitor.visitAnyKotlinMetadata( withArg { it.name shouldBe "Obfuscated" }, ofType(KotlinMetadata::class) ) } } "Then the callable reference class should not mention the original name" { programClassPool.classesAccept( "TestKt\$ref\$1", AllConstantVisitor( object : ConstantVisitor { override fun visitAnyConstant(clazz: Clazz, constant: Constant) { constant.toString() shouldNotContainIgnoringCase "original" } } ) ) } } "Given a Java field callable reference" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ val javaClassInstance = Original() fun ref() = javaClassInstance::original """.trimIndent() ), JavaSource( "Original.java", """ public class Original { public String original = "bar"; } """.trimIndent() ) ) programClassPool.classesAccept( MultiClassVisitor( ClassNameFilter( "Original", ClassRenamer( { "Obfuscated" }, { clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) } ) ), createFixer(programClassPool, libraryClassPool) ) ) val callableRefInfoVisitor = spyk() val ownerVisitor = spyk< KotlinMetadataVisitor>() val testVisitor = createVisitor(callableRefInfoVisitor, ownerVisitor) programClassPool.classesAccept("TestKt\$ref\$1", testVisitor) "Then the callableReferenceInfo should be initialized" { verify(exactly = 1) { callableRefInfoVisitor.visitJavaReferenceInfo( withArg { it.name shouldBe "obfuscated" it.signature shouldBe "getObfuscated()Ljava/lang/String;" it.owner shouldBe null } ) } verify(exactly = 0) { ownerVisitor.visitAnyKotlinMetadata( withArg { it.name shouldBe "Obfuscated" }, ofType(KotlinMetadata::class) ) } } "Then the callable reference class should not mention the original name" { programClassPool.classesAccept( "TestKt\$ref\$1", AllConstantVisitor( object : ConstantVisitor { override fun visitAnyConstant(clazz: Clazz, constant: Constant) { constant.toString() shouldNotContainIgnoringCase "original" } } ) ) } } }) private fun createVisitor(callableRefInfoVisitor: CallableReferenceInfoVisitor, ownerVisitor: KotlinMetadataVisitor) = ReferencedKotlinMetadataVisitor( object : KotlinMetadataVisitor { override fun visitAnyKotlinMetadata(clazz: Clazz, kotlinMetadata: KotlinMetadata) {} override fun visitKotlinSyntheticClassMetadata(clazz: Clazz, classMetadata: KotlinSyntheticClassKindMetadata) { classMetadata.callableReferenceInfoAccept(callableRefInfoVisitor) classMetadata.callableReferenceInfoAccept { it.ownerAccept(ownerVisitor) } } } ) private fun createFixer(programClassPool: ClassPool, libraryClassPool: ClassPool) = MultiClassVisitor( MemberReferenceFixer(false), ClassReferenceFixer(true), ReferencedKotlinMetadataVisitor(KotlinCallableReferenceFixer(programClassPool, libraryClassPool)), ConstantPoolShrinker() ) ================================================ FILE: base/src/test/kotlin/proguard/obfuscate/kotlin/KotlinIntrinsicsReplacementSequencesTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard.obfuscate.kotlin import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import proguard.classfile.Clazz import proguard.classfile.Method import proguard.classfile.attribute.CodeAttribute import proguard.classfile.attribute.visitor.AllAttributeVisitor import proguard.classfile.constant.StringConstant import proguard.classfile.constant.visitor.ConstantVisitor import proguard.classfile.editor.InstructionSequenceBuilder import proguard.classfile.instruction.Instruction import proguard.classfile.instruction.visitor.AllInstructionVisitor import proguard.classfile.instruction.visitor.InstructionVisitor import proguard.classfile.kotlin.KotlinConstants.KOTLIN_INTRINSICS_CLASS import proguard.classfile.util.InstructionSequenceMatcher import proguard.classfile.util.InstructionSequenceMatcher.X import proguard.classfile.visitor.AllMemberVisitor import proguard.classfile.visitor.MultiClassVisitor import proguard.obfuscate.util.InstructionSequenceObfuscator import proguard.testutils.ClassPoolBuilder import proguard.testutils.KotlinSource import proguard.util.ProcessingFlagSetter import proguard.util.ProcessingFlags class KotlinIntrinsicsReplacementSequencesTest : FreeSpec({ "Given a class with a lateinit variable" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ lateinit var lateVar : String """ ) ) val builder = InstructionSequenceBuilder(programClassPool, libraryClassPool) val intrinsicsSequence = builder.ldc_(X) .invokestatic( KOTLIN_INTRINSICS_CLASS, "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V" ).instructions() val constants = builder.constants() val intrinsicsSequenceMatcher = InstructionSequenceMatcher(constants, intrinsicsSequence) "Then the compiler generates an Intrinsics.throwUninitializedPropertyAccessException call" { var hasMatched = false programClassPool.classesAccept( AllAttributeVisitor( true, AllInstructionVisitor( object : InstructionVisitor { override fun visitAnyInstruction(clazz: Clazz, method: Method, codeAttribute: CodeAttribute, offset: Int, instruction: Instruction) { instruction.accept(clazz, method, codeAttribute, offset, intrinsicsSequenceMatcher) if (intrinsicsSequenceMatcher.isMatching) hasMatched = true } } ) ) ) hasMatched shouldBe true } } "Given a class with a lateinit variable without the DONT_OBFUSCATE flag" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ lateinit var lateVar : String """ ) ) val builder = InstructionSequenceBuilder(programClassPool, libraryClassPool) val intrinsicsSequence = builder.ldc_(X) .invokestatic( KOTLIN_INTRINSICS_CLASS, "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V" ).instructions() val constants = builder.constants() val intrinsicsSequenceMatcher = InstructionSequenceMatcher(constants, intrinsicsSequence) "Then InstructionSequenceObfuscator replaces the 'lateVar' with an empty string" { lateinit var matchedConstant: String programClassPool.classesAccept( MultiClassVisitor( InstructionSequenceObfuscator(KotlinIntrinsicsReplacementSequences(programClassPool, libraryClassPool)), AllAttributeVisitor( true, AllInstructionVisitor( object : InstructionVisitor { override fun visitAnyInstruction(clazz: Clazz, method: Method, codeAttribute: CodeAttribute, offset: Int, instruction: Instruction) { instruction.accept(clazz, method, codeAttribute, offset, intrinsicsSequenceMatcher) if (intrinsicsSequenceMatcher.isMatching) { var constantIndex = intrinsicsSequenceMatcher.matchedConstantIndex(X) clazz.constantPoolEntryAccept( constantIndex, object : ConstantVisitor { override fun visitStringConstant(clazz: Clazz, stringConstant: StringConstant) { matchedConstant = stringConstant.getString(clazz) } } ) } } } ) ) ) ) matchedConstant shouldBe "" } } "Given a class with a lateinit variable and the DONT_OBFUSCATE flag" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ lateinit var lateVar : String """ ) ) val builder = InstructionSequenceBuilder(programClassPool, libraryClassPool) val intrinsicsSequence = builder.ldc_(X) .invokestatic( KOTLIN_INTRINSICS_CLASS, "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V" ).instructions() val constants = builder.constants() val intrinsicsSequenceMatcher = InstructionSequenceMatcher(constants, intrinsicsSequence) programClassPool.classesAccept(AllMemberVisitor(AllAttributeVisitor(ProcessingFlagSetter(ProcessingFlags.DONT_OBFUSCATE)))) "Then InstructionSequenceObfuscator doesn't replace the 'lateVar' if DONT_OBFUSCATE is set" { lateinit var matchedConstant: String programClassPool.classesAccept( MultiClassVisitor( InstructionSequenceObfuscator(KotlinIntrinsicsReplacementSequences(programClassPool, libraryClassPool)), AllAttributeVisitor( true, AllInstructionVisitor( object : InstructionVisitor { override fun visitAnyInstruction(clazz: Clazz, method: Method, codeAttribute: CodeAttribute, offset: Int, instruction: Instruction) { instruction.accept(clazz, method, codeAttribute, offset, intrinsicsSequenceMatcher) if (intrinsicsSequenceMatcher.isMatching) { var constantIndex = intrinsicsSequenceMatcher.matchedConstantIndex(X) clazz.constantPoolEntryAccept( constantIndex, object : ConstantVisitor { override fun visitStringConstant(clazz: Clazz, stringConstant: StringConstant) { matchedConstant = stringConstant.getString(clazz) } } ) } } } ) ) ) ) matchedConstant shouldBe "lateVar" } } }) ================================================ FILE: base/src/test/kotlin/proguard/obfuscate/kotlin/KotlinValueParameterNameShrinkerTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.obfuscate.kotlin import io.kotest.core.spec.IsolationMode import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.collections.shouldExistInOrder import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockkStatic import io.mockk.spyk import io.mockk.verify import proguard.classfile.kotlin.KotlinClassKindMetadata import proguard.classfile.kotlin.KotlinConstructorMetadata import proguard.classfile.kotlin.KotlinFunctionMetadata import proguard.classfile.kotlin.KotlinPropertyMetadata import proguard.classfile.kotlin.visitor.AllConstructorVisitor import proguard.classfile.kotlin.visitor.AllFunctionVisitor import proguard.classfile.kotlin.visitor.AllPropertyVisitor import proguard.classfile.kotlin.visitor.AllValueParameterVisitor import proguard.classfile.kotlin.visitor.KotlinValueParameterVisitor import proguard.testutils.ClassPoolBuilder import proguard.testutils.KotlinSource import proguard.classfile.kotlin.KotlinValueParameterMetadata as ValueParameter import proguard.obfuscate.kotlin.KotlinValueParameterUsageMarker as VpUsageMarker class KotlinValueParameterNameShrinkerTest : FreeSpec({ // InstancePerTest so the names are reset before every test isolationMode = IsolationMode.InstancePerTest val (programClassPool, _) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ @Suppress("UNUSED_PARAMETER") class Foo(param1: String, param2: String, param3: String) { var property: String = "foo" set(param1) { } fun foo(param1: String, param2: String, param3: String) {} } """.trimIndent() ) ) "Given a class with value parameters" - { val clazz = programClassPool.getClass("Foo") "When none are marked unused" - { "Then they should keep their names" { val valueParameterVisitor = spyk() mockkStatic(VpUsageMarker::class) every { VpUsageMarker.isUsed(any()) } returns true clazz.kotlinMetadataAccept(KotlinValueParameterNameShrinker()) clazz.kotlinMetadataAccept( AllConstructorVisitor( AllValueParameterVisitor( valueParameterVisitor ) ) ) val valueParameters = mutableListOf() verify { valueParameterVisitor.visitConstructorValParameter( clazz, ofType(KotlinClassKindMetadata::class), ofType(KotlinConstructorMetadata::class), capture(valueParameters) ) } valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>( { it.parameterName == "param1" }, { it.parameterName == "param2" }, { it.parameterName == "param3" } ) } } "When all are marked unused" - { "Then they should be renamed" { val valueParameterVisitor = spyk() mockkStatic(VpUsageMarker::class) every { VpUsageMarker.isUsed(any()) } returns false clazz.kotlinMetadataAccept(KotlinValueParameterNameShrinker()) clazz.kotlinMetadataAccept( AllConstructorVisitor( AllValueParameterVisitor( valueParameterVisitor ) ) ) val valueParameters = mutableListOf() verify { valueParameterVisitor.visitConstructorValParameter( clazz, ofType(KotlinClassKindMetadata::class), ofType(KotlinConstructorMetadata::class), capture(valueParameters) ) } valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>( { it.parameterName == "p0" }, { it.parameterName == "p1" }, { it.parameterName == "p2" } ) } } "When some are marked used" - { "Then only the unused ones should be renamed" { val valueParameterVisitor = spyk() val valueParameters = mutableListOf() mockkStatic(VpUsageMarker::class) every { VpUsageMarker.isUsed(match { it is ValueParameter && it.parameterName == "param2" }) } returns true clazz.kotlinMetadataAccept(KotlinValueParameterNameShrinker()) clazz.kotlinMetadataAccept( AllConstructorVisitor( AllValueParameterVisitor( valueParameterVisitor ) ) ) verify(exactly = 3) { valueParameterVisitor.visitConstructorValParameter( clazz, ofType(KotlinClassKindMetadata::class), ofType(KotlinConstructorMetadata::class), capture(valueParameters) ) } valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>( { it.parameterName == "p0" }, { it.parameterName == "param2" }, { it.parameterName == "p1" } ) } } } "Given a function with value parameters" - { val clazz = programClassPool.getClass("Foo") "When none are marked unused" - { "Then they should keep their names" { val valueParameterVisitor = spyk() mockkStatic(VpUsageMarker::class) every { VpUsageMarker.isUsed(any()) } returns true clazz.kotlinMetadataAccept(KotlinValueParameterNameShrinker()) clazz.kotlinMetadataAccept( AllFunctionVisitor( AllValueParameterVisitor( valueParameterVisitor ) ) ) val valueParameters = mutableListOf() verify { valueParameterVisitor.visitFunctionValParameter( clazz, ofType(KotlinClassKindMetadata::class), ofType(KotlinFunctionMetadata::class), capture(valueParameters) ) } valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>( { it.parameterName == "param1" }, { it.parameterName == "param2" }, { it.parameterName == "param3" } ) } } "When all are marked unused" - { "Then they should be renamed" { val valueParameterVisitor = spyk() mockkStatic(VpUsageMarker::class) every { VpUsageMarker.isUsed(any()) } returns false clazz.kotlinMetadataAccept(KotlinValueParameterNameShrinker()) clazz.kotlinMetadataAccept( AllFunctionVisitor( AllValueParameterVisitor( valueParameterVisitor ) ) ) val valueParameters = mutableListOf() verify { valueParameterVisitor.visitFunctionValParameter( clazz, ofType(KotlinClassKindMetadata::class), ofType(KotlinFunctionMetadata::class), capture(valueParameters) ) } valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>( { it.parameterName == "p0" }, { it.parameterName == "p1" }, { it.parameterName == "p2" } ) } } "When some are marked used" - { "Then only the unused ones should be renamed" { val valueParameterVisitor = spyk() val valueParameters = mutableListOf() mockkStatic(VpUsageMarker::class) every { VpUsageMarker.isUsed(match { it is ValueParameter && it.parameterName == "param2" }) } returns true clazz.kotlinMetadataAccept(KotlinValueParameterNameShrinker()) clazz.kotlinMetadataAccept( AllFunctionVisitor( AllValueParameterVisitor( valueParameterVisitor ) ) ) verify(exactly = 3) { valueParameterVisitor.visitFunctionValParameter( clazz, ofType(KotlinClassKindMetadata::class), ofType(KotlinFunctionMetadata::class), capture(valueParameters) ) } valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>( { it.parameterName == "p0" }, { it.parameterName == "param2" }, { it.parameterName == "p1" } ) } } } "Given a property with value parameters" - { val clazz = programClassPool.getClass("Foo") "When none are marked unused" - { "Then they should keep their names" { val valueParameterVisitor = spyk() mockkStatic(VpUsageMarker::class) every { VpUsageMarker.isUsed(any()) } returns true clazz.kotlinMetadataAccept(KotlinValueParameterNameShrinker()) clazz.kotlinMetadataAccept( AllPropertyVisitor( AllValueParameterVisitor( valueParameterVisitor ) ) ) verify { valueParameterVisitor.visitPropertyValParameter( clazz, ofType(KotlinClassKindMetadata::class), ofType(KotlinPropertyMetadata::class), withArg { it.parameterName shouldBe "param1" } ) } } } "When all are marked unused" - { "Then they should be renamed" { val valueParameterVisitor = spyk() mockkStatic(VpUsageMarker::class) every { VpUsageMarker.isUsed(any()) } returns false clazz.kotlinMetadataAccept(KotlinValueParameterNameShrinker()) clazz.kotlinMetadataAccept( AllPropertyVisitor( AllValueParameterVisitor( valueParameterVisitor ) ) ) verify { valueParameterVisitor.visitPropertyValParameter( clazz, ofType(KotlinClassKindMetadata::class), ofType(KotlinPropertyMetadata::class), withArg { it.parameterName shouldBe "p0" } ) } } } } }) ================================================ FILE: base/src/test/kotlin/proguard/optimize/KotlinContextReceiverParameterShrinkingTest.kt ================================================ package proguard.optimize import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.mockk.spyk import io.mockk.verify import proguard.AppView import proguard.classfile.ClassPool import proguard.classfile.Clazz import proguard.classfile.Member import proguard.classfile.kotlin.visitor.KotlinMetadataPrinter import proguard.classfile.util.AllParameterVisitor import proguard.classfile.visitor.ClassCleaner import proguard.classfile.visitor.ClassPrinter import proguard.classfile.visitor.MemberNameFilter import proguard.classfile.visitor.ParameterVisitor import proguard.mark.Marker import proguard.testutils.ClassPoolBuilder import proguard.testutils.KotlinSource import testutils.asConfiguration import java.io.PrintWriter import java.io.StringWriter class KotlinContextReceiverParameterShrinkingTest : StringSpec({ // TODO(T18173): These tests currently check that the Context Receivers list // is *not* shrunk and that the JVM method parameter is *not* removed. // Ideally, we would be able to shrink the context receivers list in-sync // with existing optimizations. "Given a Kotlin function with ContextReceivers" { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ class Unused { fun info(message: String) { println(message) } } class Used { fun used(message: String) { } } context(Used, Unused) fun String.foo() { used(this) } fun main() { with (Unused()) { with (Used()) { "test".foo() } } } """.trimIndent() ), kotlincArguments = listOf( "-Xcontext-receivers", // Disable generation of instrinsics null checks // In real world use-cases, -assumenosideeffects might be used "-Xno-param-assertions" ) ) optimize(programClassPool, libraryClassPool) val testKt = programClassPool.getClass("TestKt") val kotlinMetadata = testKt.kotlinMetadataAsString kotlinMetadata shouldContain "[CTRE] Unused" kotlinMetadata shouldContain "[CTRE] Used" verifyParameters(testKt, "foo*") } "Given a Kotlin function with default parameters with ContextReceivers" { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ class Unused { fun info(message: String) { println(message) } } class Used { fun bar(message: String) { } } context(Used, Unused) fun String.foo(message: String = "test") { println(this) // use the String receiver bar(message) } fun main() { with (Unused()) { with (Used()) { "test".foo() } } } """.trimIndent() ), kotlincArguments = listOf( "-Xcontext-receivers", // Disable generation of instrinsics null checks // In real world use-cases, -assumenosideeffects might be used "-Xno-param-assertions" ) ) optimize(programClassPool, libraryClassPool) val testKt = programClassPool.getClass("TestKt") val kotlinMetadata = testKt.kotlinMetadataAsString kotlinMetadata shouldContain "[CTRE] Unused" kotlinMetadata shouldContain "[CTRE] Used" testKt.accept(ClassPrinter()) verifyParameters(testKt, "foo*") } "Given a Kotlin property with ContextReceivers used in the getter" { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ class Unused { fun info(message: String) { println(message) } } class Used { fun barfo(message: String) { } } context(Used, Unused) val String.property: String get() { println(this) barfo("bar") return "test" } fun main() { with (Unused()) { with (Used()) { "test".property } } } """.trimIndent() ), kotlincArguments = listOf( "-Xcontext-receivers", // Disable generation of instrinsics null checks // In real world use-cases, -assumenosideeffects might be used "-Xno-param-assertions" ) ) optimize(programClassPool, libraryClassPool) val testKt = programClassPool.getClass("TestKt") val kotlinMetadata = testKt.kotlinMetadataAsString kotlinMetadata shouldContain "[CTRE] Unused" kotlinMetadata shouldContain "[CTRE] Used" verifyParameters(testKt, "get*,set*") } "Given a Kotlin property with a single ContextReceivers used both in the getter and setter" { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ class Used2 { fun used2(message: String) { println(message) } } class Used { fun used(message: String) { } } context(Used, Used2) var String.property: String set(value) { used(value) used2(value) } get() { used("bar") used2(this) return "test" } fun main() { with (Used2()) { with (Used()) { "test".property } } } """.trimIndent() ), kotlincArguments = listOf( "-Xcontext-receivers", // Disable generation of instrinsics null checks // In real world use-cases, -assumenosideeffects might be used "-Xno-param-assertions" ) ) optimize(programClassPool, libraryClassPool) val testKt = programClassPool.getClass("TestKt") val kotlinMetadata = testKt.kotlinMetadataAsString kotlinMetadata shouldContain "[CTRE] Used2" kotlinMetadata shouldContain "[CTRE] Used" verifyParameters(testKt, "get*,set*", p1 = "LUsed;", p2 = "LUsed2;") } "Given a Kotlin property with a single ContextReceivers used in only the setter" { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ class Used2 { fun used2(message: String) { println(message) } } class Used { fun used(message: String) { } } context(Used, Used2) var String.property: String set(value) { used2(value) } get() { return "test" } fun main() { with (Used2()) { with (Used()) { "test".property } } } """.trimIndent() ), kotlincArguments = listOf( "-Xcontext-receivers", // Disable generation of instrinsics null checks // In real world use-cases, -assumenosideeffects might be used "-Xno-param-assertions" ) ) optimize(programClassPool, libraryClassPool) val testKt = programClassPool.getClass("TestKt") val kotlinMetadata = testKt.kotlinMetadataAsString kotlinMetadata shouldContain "[CTRE] Used2" kotlinMetadata shouldContain "[CTRE] Used" verifyParameters(testKt, "get*,set*", p1 = "LUsed;", p2 = "LUsed2;") } "Given a Kotlin property with ContextReceivers used in only the getter but also containing a setter" { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ class Unused { fun info(message: String) { println(message) } } class Used { fun used(message: String) { } } context(Used, Unused) var String.property: String set(value) { } get() { used(this) return "test" } fun main() { with (Unused()) { with (Used()) { "test".property } } } """.trimIndent() ), kotlincArguments = listOf( "-Xcontext-receivers", // Disable generation of instrinsics null checks // In real world use-cases, -assumenosideeffects might be used "-Xno-param-assertions" ) ) optimize(programClassPool, libraryClassPool) val testKt = programClassPool.getClass("TestKt") val kotlinMetadata = testKt.kotlinMetadataAsString kotlinMetadata shouldContain "[CTRE] Unused" kotlinMetadata shouldContain "[CTRE] Used" verifyParameters(testKt, "get*,set*") } "Given a Kotlin property with ContextReceivers with no getter or setter" { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ class Unused1 class Unused2 class ClassWithProperty { context(Unused1, Unused2) val property: String by lazy { "foo" } } fun main() { with (Unused2()) { with (Unused1()) { ClassWithProperty().property } } } """.trimIndent() ), kotlincArguments = listOf( "-Xcontext-receivers", // Disable generation of instrinsics null checks // In real world use-cases, -assumenosideeffects might be used "-Xno-param-assertions" ) ) val classWithProperty = programClassPool.getClass("ClassWithProperty") optimize(programClassPool, libraryClassPool) val kotlinMetadata = classWithProperty.kotlinMetadataAsString kotlinMetadata shouldContain "[CTRE] Unused1" kotlinMetadata shouldContain "[CTRE] Unused2" verifyParameters(classWithProperty, "get*", p1 = "LUnused1;", p2 = "LUnused2;") } "Given a Kotlin class with ContextReceivers" { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ class Unused { fun info(message: String) { println(message) } } class Used { fun barfo(message: String) { } } context(Used, Unused) class MyClass { fun foo() { barfo("test") } } fun main() { with (Unused()) { with (Used()) { MyClass().foo() } } } """.trimIndent() ), kotlincArguments = listOf( "-Xcontext-receivers", // Disable generation of instrinsics null checks // In real world use-cases, -assumenosideeffects might be used "-Xno-param-assertions" ) ) optimize(programClassPool, libraryClassPool) val myClass = programClassPool.getClass("MyClass") val kotlinMetadata = myClass.kotlinMetadataAsString kotlinMetadata shouldContain "[CTRE] Unused" kotlinMetadata shouldContain "[CTRE] Used" verifyParameters(myClass, "") } }) private fun verifyParameters(testKt: Clazz, s: String, p1: String = "LUsed;", p2: String = "LUnused;") { val parameterVisitor = spyk() testKt.methodsAccept(MemberNameFilter(s, AllParameterVisitor(false, parameterVisitor))) verify { parameterVisitor.visitParameter( testKt, ofType(), ofType(), ofType(), ofType(), ofType(), withArg { it shouldBe p1 }, ofType() ) parameterVisitor.visitParameter( testKt, ofType(), ofType(), ofType(), ofType(), ofType(), withArg { it shouldBe p2 }, ofType() ) } } private fun optimize(programClassPool: ClassPool, libraryClassPool: ClassPool) { val config = """ -keep,allowoptimization,allowshrinking class TestKt,ClassWithProperty,MyClass { *; } -optimizations ** """.asConfiguration() val appView = AppView(programClassPool, libraryClassPool) programClassPool.classesAccept { it.accept(ClassCleaner()) } Marker(config).execute(appView) val optimizer = Optimizer(config) // Run two passes, two ensure that e.g. unread fields are removed, // which can then allow parameters to then be removed. optimizer.execute(appView) optimizer.execute(appView) } private val Clazz.kotlinMetadataAsString: String get() { val stringWriter = StringWriter() kotlinMetadataAccept(KotlinMetadataPrinter(PrintWriter(stringWriter))) return stringWriter.toString() } ================================================ FILE: base/src/test/kotlin/proguard/optimize/MemberDescriptorSpecializerTest.kt ================================================ package proguard.optimize import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import proguard.classfile.AccessConstants import proguard.classfile.Clazz import proguard.classfile.Member import proguard.classfile.attribute.visitor.AllAttributeVisitor import proguard.classfile.attribute.visitor.DebugAttributeVisitor import proguard.classfile.visitor.AllMemberVisitor import proguard.classfile.visitor.AllMethodVisitor import proguard.classfile.visitor.ClassAccessFilter import proguard.classfile.visitor.MemberNameFilter import proguard.classfile.visitor.MemberVisitor import proguard.classfile.visitor.ParallelAllClassVisitor.ClassVisitorFactory import proguard.evaluation.InvocationUnit import proguard.evaluation.PartialEvaluator import proguard.evaluation.value.ParticularValueFactory import proguard.evaluation.value.ValueFactory import proguard.optimize.evaluation.StoringInvocationUnit import proguard.optimize.info.ProgramClassOptimizationInfoSetter import proguard.optimize.info.ProgramMemberOptimizationInfoSetter import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource import proguard.util.ProcessingFlagSetter import proguard.util.ProcessingFlags.IS_CLASS_AVAILABLE class MemberDescriptorSpecializerTest : FreeSpec({ "Given a method with a more general parameter type than its use" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( JavaSource( "Test.java", """ public class Test { public static void main(String[] args) { foo(new Foo()); } public static void foo(Bar foo) { System.out.println(foo); } } class Bar { } class Foo extends Bar { } """.trimIndent() ) ) "When specializing the member descriptors" - { // Mark all members as available. programClassPool.classesAccept(AllMemberVisitor(ProcessingFlagSetter(IS_CLASS_AVAILABLE))) // Setup the OptimizationInfo on the classes val keepMarker = KeepMarker() libraryClassPool.classesAccept(keepMarker) libraryClassPool.classesAccept(AllMemberVisitor(keepMarker)) programClassPool.classesAccept(ProgramClassOptimizationInfoSetter()) programClassPool.classesAccept(AllMemberVisitor(ProgramMemberOptimizationInfoSetter())) // Create the optimization as in Optimizer val fillingOutValuesClassVisitor = ClassVisitorFactory { val valueFactory: ValueFactory = ParticularValueFactory() val storingInvocationUnit: InvocationUnit = StoringInvocationUnit( valueFactory, true, true, true ) ClassAccessFilter( 0, AccessConstants.SYNTHETIC, AllMethodVisitor( AllAttributeVisitor( DebugAttributeVisitor( "Filling out fields, method parameters, and return values", PartialEvaluator( valueFactory, storingInvocationUnit, true ) ) ) ) ) } programClassPool.classesAccept(fillingOutValuesClassVisitor.createClassVisitor()) // Specialize class member descriptors, based on partial evaluation. programClassPool.classesAccept( AllMemberVisitor( OptimizationInfoMemberFilter( MemberDescriptorSpecializer( true, true, true, null, null, null ) ) ) ) "Then the member descriptor should be correctly specialised" { lateinit var memberDescriptor: String programClassPool.classAccept( "Test", AllMemberVisitor( MemberNameFilter( "foo*", object : MemberVisitor { override fun visitAnyMember(clazz: Clazz, member: Member) { memberDescriptor = member.getDescriptor(clazz) } } ) ) ) memberDescriptor shouldBe "(LFoo;)V" } } } }) ================================================ FILE: base/src/test/kotlin/proguard/optimize/SimpleEnumClassCheckerTest.kt ================================================ package proguard.optimize import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import proguard.optimize.evaluation.SimpleEnumClassChecker import proguard.optimize.info.ProgramClassOptimizationInfo import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource class SimpleEnumClassCheckerTest : FreeSpec({ "Given a enum class without instance methods" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Enum.java", """ public enum Enum { FOO, BAR } """.trimIndent() ) ) "Then when checked with SimpleEnumClassChecker" - { val enumClass = programClassPool.getClass("Enum") enumClass.processingInfo = ProgramClassOptimizationInfo() enumClass.accept(SimpleEnumClassChecker()) "It should be marked as simple" { (enumClass.processingInfo as ProgramClassOptimizationInfo).isSimpleEnum shouldBe true } } } "Given a enum class with a public instance method" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Enum.java", """ public enum Enum { FOO, BAR; public void foo() { } } """.trimIndent() ) ) "Then when checked with SimpleEnumClassChecker" - { val enumClass = programClassPool.getClass("Enum") enumClass.processingInfo = ProgramClassOptimizationInfo() enumClass.accept(SimpleEnumClassChecker()) "It should not be marked as simple" { (enumClass.processingInfo as ProgramClassOptimizationInfo).isSimpleEnum shouldBe false } } } "Given a enum class with a private instance method" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Enum.java", """ public enum Enum { FOO, BAR; private void foo() { } } """.trimIndent() ) ) "Then when checked with SimpleEnumClassChecker" - { val enumClass = programClassPool.getClass("Enum") enumClass.processingInfo = ProgramClassOptimizationInfo() enumClass.accept(SimpleEnumClassChecker()) "It should not be marked as simple" { (enumClass.processingInfo as ProgramClassOptimizationInfo).isSimpleEnum shouldBe false } } } }) ================================================ FILE: base/src/test/kotlin/proguard/optimize/gson/MarkedAnnotationDeleterTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard.optimize.gson import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.shouldBe import proguard.classfile.Clazz import proguard.classfile.attribute.annotation.Annotation import proguard.classfile.attribute.annotation.visitor.AllAnnotationVisitor import proguard.classfile.attribute.annotation.visitor.AnnotationTypeFilter import proguard.classfile.attribute.annotation.visitor.AnnotationVisitor import proguard.classfile.attribute.visitor.AllAttributeVisitor import proguard.classfile.visitor.ProcessingInfoSetter import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource class MarkedAnnotationDeleterTest : FreeSpec({ "Given a class with two annotations on its field" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource("Ann1.java", "public @interface Ann1 {}"), JavaSource("Ann2.java", "public @interface Ann2 {}"), JavaSource( "A.java", """class A { @Ann1 @Ann2 public int a; }""" ) ) val classA = programClassPool.getClass("A") "When we mark and delete the annotations" - { val mark = Object() classA.fieldsAccept( AllAttributeVisitor( AllAnnotationVisitor( ProcessingInfoSetter(mark) ) ) ) classA.fieldsAccept( AllAttributeVisitor( MarkedAnnotationDeleter(mark) ) ) "Then no annotations should remain" { var count = 0 classA.fieldsAccept( AllAttributeVisitor( AllAnnotationVisitor(object : AnnotationVisitor { override fun visitAnnotation(clazz: Clazz?, annotation: Annotation?) { count += 1 } } ) ) ) count shouldBe 0 } } } "Given a class with two A and B annotations on its field" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource("AnnA1.java", "public @interface AnnA1 {}"), JavaSource("AnnA2.java", "public @interface AnnA2 {}"), JavaSource("AnnB1.java", "public @interface AnnB1 {}"), JavaSource("AnnB2.java", "public @interface AnnB2 {}"), JavaSource("AnnB3.java", "public @interface AnnB3 {}"), JavaSource( "A.java", """class A { @AnnB1 @AnnA1 @AnnB2 @AnnA2 @AnnB3 public int a; }""" ) ) val classA = programClassPool.getClass("A") "When we mark and delete only the A annotations" - { val mark = Object() classA.fieldsAccept( AllAttributeVisitor( AllAnnotationVisitor( AnnotationTypeFilter( "LAnnA*;", ProcessingInfoSetter(mark) ) ) ) ) classA.fieldsAccept( AllAttributeVisitor( MarkedAnnotationDeleter(mark) ) ) "Then only the B annotations should remain" { val list = mutableListOf() classA.fieldsAccept( AllAttributeVisitor( AllAnnotationVisitor(object : AnnotationVisitor { override fun visitAnnotation(clazz: Clazz, annotation: Annotation) { list.add(annotation.getType(clazz)) } } ) ) ) list shouldContainExactly listOf("LAnnB1;", "LAnnB2;", "LAnnB3;") } } } "Given a class with two annotations on its method parameter" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource("Ann1.java", "public @interface Ann1 {}"), JavaSource("Ann2.java", "public @interface Ann2 {}"), JavaSource( "A.java", """class A { public void a(@Ann1 @Ann2 int x) {} }""" ) ) val classA = programClassPool.getClass("A") "When we mark and delete the annotations" - { val mark = Object() classA.methodsAccept( AllAttributeVisitor( AllAnnotationVisitor( ProcessingInfoSetter(mark) ) ) ) classA.methodsAccept( AllAttributeVisitor( MarkedAnnotationDeleter(mark) ) ) "Then no annotations should remain" { var count = 0 classA.methodsAccept( AllAttributeVisitor( AllAnnotationVisitor(object : AnnotationVisitor { override fun visitAnnotation(clazz: Clazz?, annotation: Annotation?) { count += 1 } } ) ) ) count shouldBe 0 } } } "Given a class with two A and B annotations on its method parameter" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource("AnnA1.java", "public @interface AnnA1 {}"), JavaSource("AnnA2.java", "public @interface AnnA2 {}"), JavaSource("AnnB1.java", "public @interface AnnB1 {}"), JavaSource("AnnB2.java", "public @interface AnnB2 {}"), JavaSource("AnnB3.java", "public @interface AnnB3 {}"), JavaSource( "A.java", """class A { public void a(@AnnB1 @AnnA1 @AnnB2 @AnnA2 @AnnB3 int x) {} }""" ) ) val classA = programClassPool.getClass("A") "When we mark and delete only the A annotations" - { val mark = Object() classA.methodsAccept( AllAttributeVisitor( AllAnnotationVisitor( AnnotationTypeFilter( "LAnnA*;", ProcessingInfoSetter(mark) ) ) ) ) classA.methodsAccept( AllAttributeVisitor( MarkedAnnotationDeleter(mark) ) ) "Then only the B annotations should remain" { val list = mutableListOf() classA.methodsAccept( AllAttributeVisitor( AllAnnotationVisitor(object : AnnotationVisitor { override fun visitAnnotation(clazz: Clazz, annotation: Annotation) { list.add(annotation.getType(clazz)) } } ) ) ) list shouldContainExactly listOf("LAnnB1;", "LAnnB2;", "LAnnB3;") } } } }) ================================================ FILE: base/src/test/kotlin/proguard/optimize/gson/TypeArgumentFinderTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard.optimize.gson import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.shouldBe import proguard.classfile.attribute.visitor.AllAttributeVisitor import proguard.classfile.instruction.Instruction import proguard.classfile.instruction.visitor.AllInstructionVisitor import proguard.classfile.instruction.visitor.InstructionOpCodeFilter import proguard.classfile.visitor.AllMethodVisitor import proguard.classfile.visitor.MemberNameFilter import proguard.evaluation.PartialEvaluator import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource class TypeArgumentFinderTest : FreeSpec({ "Given an aload instruction with TypeToken" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( JavaSource( "TypeToken.java", """ package com.google.gson.reflect; import java.lang.reflect.Type; public class TypeToken { Type type; public final Type getType() { return type; } } """.trimIndent() ), JavaSource( "A.java", """ import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; public class A { public void a() { Type x = new TypeToken() {}.getType(); System.out.println(x); } } """.trimIndent() ) ) "When retrieving the return type with TypeArgumentFinder" - { val partialEvaluator = PartialEvaluator() programClassPool.classAccept( "A", AllMethodVisitor( MemberNameFilter( "a", AllAttributeVisitor(partialEvaluator) ) ) ) val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, partialEvaluator) programClassPool.classAccept( "A", AllMethodVisitor( MemberNameFilter( "a", AllAttributeVisitor( AllInstructionVisitor( InstructionOpCodeFilter( intArrayOf( Instruction.OP_ALOAD.toInt(), Instruction.OP_ALOAD_0.toInt(), Instruction.OP_ALOAD_1.toInt(), Instruction.OP_ALOAD_2.toInt(), Instruction.OP_ALOAD_3.toInt() ), typeArgumentFinder ) ) ) ) ) ) "Then we obtain the return type java.lang.String" { typeArgumentFinder.typeArgumentClasses.shouldContainExactly("java/lang/String") } } } "Given an invokevirtual instruction with TypeToken" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( JavaSource( "TypeToken.java", """ package com.google.gson.reflect; import java.lang.reflect.Type; public class TypeToken { Type type; public final Type getType() { return type; } } """.trimIndent() ), JavaSource( "A.java", """ import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; public class A { public void a() { new TypeToken() {}.getType(); } } """.trimIndent() ) ) "When retrieving the return type with TypeArgumentFinder" - { val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, null) programClassPool.classAccept( "A", AllMethodVisitor( MemberNameFilter( "a", AllAttributeVisitor( AllInstructionVisitor( InstructionOpCodeFilter( intArrayOf(Instruction.OP_INVOKEVIRTUAL.toInt()), typeArgumentFinder ) ) ) ) ) ) "Then we obtain null (Note: this case is not implemented like for aload instructions)" { typeArgumentFinder.typeArgumentClasses shouldBe null } } } "Given an ldc instruction" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( JavaSource( "Z.java", """ package a.b; public class Z { public void a() { x(Z.class); } public void x(Class c) {} } """.trimIndent() ) ) "When retrieving the return type with TypeArgumentFinder" - { val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, null) programClassPool.classAccept( "a/b/Z", AllMethodVisitor( MemberNameFilter( "a", AllAttributeVisitor( AllInstructionVisitor( InstructionOpCodeFilter( intArrayOf(Instruction.OP_LDC.toInt()), typeArgumentFinder ) ) ) ) ) ) "Then we obtain the return type a.b.Z" { typeArgumentFinder.typeArgumentClasses.shouldContainExactly("a/b/Z") } } } "Given an aload instruction" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( JavaSource( "A.java", """ public class A { public void a() { String x = "text"; System.out.println(x); } } """.trimIndent() ) ) "When retrieving the return type with TypeArgumentFinder" - { val partialEvaluator = PartialEvaluator() programClassPool.classAccept( "A", AllMethodVisitor( MemberNameFilter( "a", AllAttributeVisitor(partialEvaluator) ) ) ) val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, partialEvaluator) programClassPool.classAccept( "A", AllMethodVisitor( MemberNameFilter( "a", AllAttributeVisitor( AllInstructionVisitor( InstructionOpCodeFilter( intArrayOf( Instruction.OP_ALOAD.toInt(), Instruction.OP_ALOAD_0.toInt(), Instruction.OP_ALOAD_1.toInt(), Instruction.OP_ALOAD_2.toInt(), Instruction.OP_ALOAD_3.toInt() ), typeArgumentFinder ) ) ) ) ) ) // This is not an intended use case of the TypeArgumentFinder // so it returns null "Then we obtain null" { typeArgumentFinder.typeArgumentClasses shouldBe null } } } "Given a new instruction" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( JavaSource( "Z.java", """ package a.b; public class Z { public void a() { new Z(); } } """.trimIndent() ) ) "When retrieving the return type with TypeArgumentFinder" - { val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, null) programClassPool.classAccept( "a/b/Z", AllMethodVisitor( MemberNameFilter( "a", AllAttributeVisitor( AllInstructionVisitor( InstructionOpCodeFilter( intArrayOf(Instruction.OP_NEW.toInt()), typeArgumentFinder ) ) ) ) ) ) "Then we obtain the return type a.b.Z" { typeArgumentFinder.typeArgumentClasses.shouldContainExactly("a/b/Z") } } } }) ================================================ FILE: base/src/test/kotlin/proguard/optimize/info/InstantiationClassMarkerTest.kt ================================================ package proguard.optimize.info import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import proguard.classfile.attribute.visitor.AllAttributeVisitor import proguard.classfile.instruction.visitor.AllInstructionVisitor import proguard.classfile.visitor.AllMemberVisitor import proguard.optimize.info.ProgramClassOptimizationInfo.getProgramClassOptimizationInfo import proguard.optimize.info.ProgramClassOptimizationInfo.setProgramClassOptimizationInfo import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource class InstantiationClassMarkerTest : FreeSpec({ "A class should be marked as instantiated" - { "when it is instantiated with a `new` instruction" { val (classPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Main.java", """ class A { } public class Main { public static void main(String[] args) { System.out.println(new A()); } } """.trimIndent() ) ) val classA = classPool.getClass("A") classPool.classesAccept { clazz -> setProgramClassOptimizationInfo(clazz) } classPool.classAccept( "Main", AllMemberVisitor( AllAttributeVisitor( AllInstructionVisitor( InstantiationClassMarker() ) ) ) ) getProgramClassOptimizationInfo(classA).isInstantiated shouldBe true } "when one of its subclasses is instantiated with a `new` instruction" { val (classPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Main.java", """ class A { } class B extends A { } public class Main { public static void main(String[] args) { System.out.println(new B()); } } """.trimIndent() ) ) val classA = classPool.getClass("A") val classB = classPool.getClass("B") classPool.classesAccept { clazz -> setProgramClassOptimizationInfo(clazz) } classPool.classAccept( "Main", AllMemberVisitor( AllAttributeVisitor( AllInstructionVisitor( InstantiationClassMarker() ) ) ) ) getProgramClassOptimizationInfo(classA).isInstantiated shouldBe true getProgramClassOptimizationInfo(classB).isInstantiated shouldBe true } } }) ================================================ FILE: base/src/test/kotlin/proguard/optimize/peephole/MethodInlinerTest.kt ================================================ package proguard.optimize.peephole import io.kotest.core.spec.IsolationMode import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.ints.shouldBeExactly import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldMatch import proguard.classfile.ClassPool import proguard.classfile.Clazz import proguard.classfile.Method import proguard.classfile.ProgramClass import proguard.classfile.ProgramMethod import proguard.classfile.attribute.CodeAttribute import proguard.classfile.attribute.visitor.AllAttributeVisitor import proguard.classfile.instruction.visitor.AllInstructionVisitor import proguard.classfile.visitor.AllMethodVisitor import proguard.classfile.visitor.ClassPrinter import proguard.classfile.visitor.ClassVisitor import proguard.classfile.visitor.MemberVisitor import proguard.classfile.visitor.MultiClassVisitor import proguard.classfile.visitor.MultiMemberVisitor import proguard.optimize.info.BackwardBranchMarker import proguard.optimize.info.ProgramClassOptimizationInfoSetter import proguard.optimize.info.ProgramMemberOptimizationInfoSetter import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource import java.io.ByteArrayOutputStream import java.io.PrintWriter class MethodInlinerTest : FreeSpec({ isolationMode = IsolationMode.InstancePerTest "Given two simple functions, one calling the other" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Foo.java", """class Foo { static int f1() { return f2() + 1; } static int f2() { return 1; } }""" ) ) // Sanity check how the instructions look before. val instructionsBefore = printProgramMethodInstructions(programClassPool, "Foo", "f1", "()I") instructionsBefore.size shouldBe 4 instructionsBefore[0] shouldMatch """(.*)invokestatic(.*)Foo.f2\(\)I(.*)""".toRegex() instructionsBefore[1] shouldContain "iconst_1" instructionsBefore[2] shouldContain "iadd" instructionsBefore[3] shouldContain "ireturn" "When calling the method inliner, specifying that we should always inline" - { // Initialize optimization info (used when inlining). val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor( ProgramClassOptimizationInfoSetter(), AllMethodVisitor( ProgramMemberOptimizationInfoSetter() ) ) programClassPool.classesAccept(optimizationInfoInitializer) // Create a mock method inliner which always returns true. val methodInliner = object : MethodInliner(false, true, true) { override fun shouldInline(clazz: Clazz, method: Method?, codeAttribute: CodeAttribute?): Boolean = true } programClassPool.classesAccept( AllMethodVisitor( AllAttributeVisitor( methodInliner ) ) ) "Then the called function is inlined as expected" { val instructionsAfter = printProgramMethodInstructions(programClassPool, "Foo", "f1", "()I") instructionsAfter.size shouldBe 4 instructionsAfter[0] shouldContain "iconst_1" instructionsAfter[1] shouldContain "iconst_1" instructionsAfter[2] shouldContain "iadd" instructionsAfter[3] shouldContain "ireturn" } } "When calling the method inliner, specifying that we should never inline" - { // Initialize optimization info (used when inlining). val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor( ProgramClassOptimizationInfoSetter(), AllMethodVisitor( ProgramMemberOptimizationInfoSetter() ) ) programClassPool.classesAccept(optimizationInfoInitializer) // Create a mock method inliner which always returns true. val methodInliner = object : MethodInliner(false, true, true) { override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = false } programClassPool.classesAccept( AllMethodVisitor( AllAttributeVisitor( methodInliner ) ) ) "Then the called function is not inlined" { val instructionsAfter = printProgramMethodInstructions(programClassPool, "Foo", "f1", "()I") instructionsAfter.size shouldBe 4 instructionsAfter[0] shouldMatch """(.*)invokestatic(.*)Foo.f2\(\)I(.*)""".toRegex() instructionsAfter[1] shouldContain "iconst_1" instructionsAfter[2] shouldContain "iadd" instructionsAfter[3] shouldContain "ireturn" } } } "Given a function calling a big function" - { val lotsOfPrints = (1..3000).joinToString("\n") { "System.out.println(\"${it}\");" } val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Foo.java", """class Foo { static void f1() { f2(); } static void f2() { """ + lotsOfPrints + """ } }""" ) ) val clazz = programClassPool.getClass("Foo") as ProgramClass val method = clazz.findMethod("f1", "()V") as ProgramMethod val codeAttr = method.attributes.filterIsInstance()[0] val lengthBefore = codeAttr.u4codeLength "When using the default maximum resulting code length parameter" - { // Initialize optimization info (used when inlining). val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor( ProgramClassOptimizationInfoSetter(), AllMethodVisitor( ProgramMemberOptimizationInfoSetter() ) ) programClassPool.classesAccept(optimizationInfoInitializer) // Create a mock method inliner which always returns true. val methodInliner = object : MethodInliner(false, true, true) { override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = true } "Then the large method is not inlined" { programClassPool.classesAccept( AllMethodVisitor( AllAttributeVisitor( methodInliner ) ) ) val lengthAfter = codeAttr.u4codeLength lengthAfter shouldBeExactly lengthBefore } } "When using the maximum resulting code length parameter" - { // Initialize optimization info (used when inlining). val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor( ProgramClassOptimizationInfoSetter(), AllMethodVisitor( ProgramMemberOptimizationInfoSetter() ) ) programClassPool.classesAccept(optimizationInfoInitializer) // Create a mock method inliner with the maximum limit val methodInliner = object : MethodInliner(false, true, MAXIMUM_RESULTING_CODE_LENGTH_JVM, true, true, null) { override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = true } programClassPool.classesAccept( AllMethodVisitor( AllAttributeVisitor( methodInliner ) ) ) "Then the large method is inlined" { val lengthAfter = codeAttr.u4codeLength lengthAfter shouldBeGreaterThan lengthBefore } } } "Given a method initializing a library class and calling a method with backwards branching " - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Foo.java", """class Foo { static void f1() { StringBuilder sb = new StringBuilder(); sb.append(System.currentTimeMillis()); System.out.println(sb.toString()); f2(); } static void f2() { for (int i = 0; i < 1000; i++) { System.out.println(i); } } }""" ) ) val clazz = programClassPool.getClass("Foo") as ProgramClass val method = clazz.findMethod("f1", "()V") as ProgramMethod val codeAttr = method.attributes.filterIsInstance()[0] val lengthBefore = codeAttr.u4codeLength "When inlining the method call" - { // Initialize optimization info (used when inlining). // Make sure the backwards branching info is set correctly. val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor( ProgramClassOptimizationInfoSetter(), AllMethodVisitor( MultiMemberVisitor( ProgramMemberOptimizationInfoSetter(), AllAttributeVisitor( AllInstructionVisitor( BackwardBranchMarker() ) ) ) ) ) programClassPool.classesAccept(optimizationInfoInitializer) // Create a mock method inliner which always returns true. val methodInliner = object : MethodInliner(false, true, true) { override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = true } "Then the method is inlined" { programClassPool.classesAccept( AllMethodVisitor( AllAttributeVisitor(methodInliner) ) ) val lengthAfter = codeAttr.u4codeLength lengthAfter shouldBeGreaterThan lengthBefore } } } }) private fun printProgramMethodInstructions( classPool: ClassPool, className: String, methodName: String, methodDescriptor: String ): List { val output = ByteArrayOutputStream() val pw = PrintWriter(output) classPool.classAccept(className) { clazz -> clazz.methodAccept( methodName, methodDescriptor, object : MemberVisitor { override fun visitProgramMethod(programClass: ProgramClass?, programMethod: ProgramMethod?) { programMethod?.accept(programClass, AllAttributeVisitor(AllInstructionVisitor(ClassPrinter(pw)))) } } ) } pw.flush() val result = output.toString().trimEnd() return result.lines() } ================================================ FILE: base/src/test/kotlin/proguard/shrink/ClassUsageMarkerTest.kt ================================================ package proguard.shrink import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.Matcher import io.kotest.matchers.MatcherResult import io.kotest.matchers.should import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNot import io.kotest.matchers.shouldNotBe import proguard.AppView import proguard.Configuration import proguard.classfile.attribute.annotation.visitor.AllElementValueVisitor import proguard.classfile.attribute.visitor.AllAttributeVisitor import proguard.classfile.util.EnumFieldReferenceInitializer import proguard.classfile.visitor.AllMethodVisitor import proguard.classfile.visitor.MultiClassVisitor import proguard.testutils.ClassPoolBuilder import proguard.testutils.JavaSource import proguard.testutils.KotlinSource import proguard.util.Processable import proguard.util.ProcessingFlagSetter import proguard.util.ProcessingFlags.DONT_SHRINK import proguard.util.kotlin.asserter.KotlinMetadataVerifier private fun beMarkedWith(simpleUsageMarker: SimpleUsageMarker) = object : Matcher { override fun test(value: Processable) = MatcherResult( simpleUsageMarker.isUsed(value), { "$value should be marked" }, { "$value should not be used" } ) } class ClassUsageMarkerTest : StringSpec({ "Class Usage Marking should mark methods invoked in the method body" { val (classPool, _) = ClassPoolBuilder.fromSource( JavaSource( "A.java", """ public class A { public void method1() { this.method2(); } public void method2() { this.method2(); } public void method3() { B.method4(); } } """.trimIndent() ), JavaSource( "B.java", """ public class B { public static void method4() { new A().method2(); } } """.trimIndent() ) ) val classA = classPool.getClass("A") val method1 = classA.findMethod("method1", null) val method2 = classA.findMethod("method2", null) val method3 = classA.findMethod("method3", null) val classB = classPool.getClass("B") val method4 = classB.findMethod("method4", null) val usageMarker = SimpleUsageMarker() val classUsageMarker = ClassUsageMarker(usageMarker) // Visiting the class does not imply visiting the methods classA.accept(classUsageMarker) method1 shouldNot beMarkedWith(usageMarker) method2 shouldNot beMarkedWith(usageMarker) method3 shouldNot beMarkedWith(usageMarker) method4 shouldNot beMarkedWith(usageMarker) // Visiting one method also marks the called method, but not the others method1.accept(classA, classUsageMarker) method1 should beMarkedWith(usageMarker) method2 should beMarkedWith(usageMarker) method3 shouldNot beMarkedWith(usageMarker) method4 shouldNot beMarkedWith(usageMarker) // Visiting the other methods, marks the last one as well method3.accept(classA, classUsageMarker) method1 should beMarkedWith(usageMarker) method2 should beMarkedWith(usageMarker) method3 should beMarkedWith(usageMarker) method4 should beMarkedWith(usageMarker) } "The comparable interface should induce additional marking" { val (classPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Application.java", """ public class Application { Other attribute; public Application() { attribute = new Other(); } } """.trimIndent() ), JavaSource( "Other.java", """ public class Other implements Comparable { public void foo() {} public void bar() {} public int compareTo(Other o) { foo(); return 0; } } """.trimIndent() ) ) val classUsageMarker = ClassUsageMarker() val applicationClazz = classPool.getClass("Application") val applicationInit = applicationClazz.findMethod("", null) applicationClazz.accept(classUsageMarker) applicationInit.accept(applicationClazz, classUsageMarker) val otherClazz = classPool.getClass("Other") val otherInit = otherClazz.findMethod("", null) val otherFoo = otherClazz.findMethod("foo", null) val otherBar = otherClazz.findMethod("bar", null) otherInit should beMarkedWith(classUsageMarker.usageMarker) otherFoo should beMarkedWith(classUsageMarker.usageMarker) otherBar shouldNot beMarkedWith(classUsageMarker.usageMarker) } "Using an enum as default value in an annotation field should mark the enum value as used" { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource( "A.java", """ public @interface A { B b() default B.Y; } """.trimIndent() ), JavaSource( "B.java", """ public enum B { X, Y, Z } """.trimIndent() ) ) // Make sure the field references in enum fields are updated. programClassPool.classesAccept( AllAttributeVisitor( true, AllElementValueVisitor( true, EnumFieldReferenceInitializer() ) ) ) // Visit attributes to mark usage processing info. programClassPool.getClass("A").accept( AllAttributeVisitor( true, ClassUsageMarker() ) ) val bClass = programClassPool.getClass("B") val xField = bClass.findField("X", null) val yField = bClass.findField("Y", null) val zField = bClass.findField("Z", null) xField.processingInfo shouldBe null yField.processingInfo shouldNotBe null zField.processingInfo shouldBe null } "Given a Kotlin interface with default method implementation in compatibility mode" { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ interface Test { fun foo() { TODO() } } """.trimIndent() ), kotlincArguments = listOf("-Xjvm-default=all") ) // Run the asserter to ensure any metadata that isn't initialized correctly is thrown away KotlinMetadataVerifier(Configuration()).execute(AppView(programClassPool, libraryClassPool)) programClassPool.classAccept("Test", AllMethodVisitor(ProcessingFlagSetter(DONT_SHRINK))) val classUsageMarker = ClassUsageMarker(SimpleUsageMarker()) shouldNotThrowAny { programClassPool.classAccept("Test", MultiClassVisitor(classUsageMarker, AllMethodVisitor(classUsageMarker))) } } }) ================================================ FILE: base/src/test/kotlin/proguard/util/ProguardAssemblerTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.util import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import proguard.classfile.ProgramClass import proguard.classfile.constant.Utf8Constant import proguard.testutils.AssemblerSource import proguard.testutils.ClassPoolBuilder class ProguardAssemblerTest : FreeSpec({ "Given Java bytecode" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( AssemblerSource( "A.jbc", """ version 1.8; public class A extends java.lang.Object [ SourceFile "A.java"; ] { public void () { line 1 aload_0 invokespecial java.lang.Object#void () return } public static void main(java.lang.String[]) { line 3 getstatic java.lang.System#java.io.PrintStream out ldc "hello" invokevirtual java.io.PrintStream#void println(java.lang.String) line 4 return } } """ ) ) "When the ClassPool object is created" - { programClassPool.shouldNotBeNull() "Then the count and name of the methods should match the bytecode" { val classA = programClassPool.getClass("A") as ProgramClass classA.methods.size shouldBe 2 (classA.constantPool[classA.methods[0].u2nameIndex] as Utf8Constant).string shouldBe "" (classA.constantPool[classA.methods[1].u2nameIndex] as Utf8Constant).string shouldBe "main" } } } }) ================================================ FILE: base/src/test/kotlin/testutils/ConfigurationUtil.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package testutils import proguard.Configuration import proguard.ConfigurationParser import proguard.ConfigurationWriter import java.io.PrintWriter import java.io.StringWriter fun String.asConfiguration(): Configuration { val configuration = Configuration() ConfigurationParser(this, "test configuration", null, System.getProperties()).use { it.parse(configuration) } return configuration } fun Configuration.asString(): String { val out = StringWriter() val configuration = Configuration() ConfigurationWriter(PrintWriter(out)).use { it.write(configuration) } return out.toString().trim() } ================================================ FILE: base/src/test/kotlin/testutils/JavaUtil.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package testutils import java.io.File import javax.lang.model.SourceVersion val currentJavaVersion: Int by lazy { var version = System.getProperty("java.version") // Strip early access suffix if (version.endsWith("-ea")) { version = version.substring(0, version.length - 3) } if (version.startsWith("1.")) { version = version.substring(2, 3) } else { val dot = version.indexOf(".") if (dot != -1) { version = version.substring(0, dot) } } return@lazy version.toInt() } fun isJava9OrLater(): Boolean = SourceVersion.latestSupported() > SourceVersion.RELEASE_8 fun getCurrentJavaHome(): File = if (isJava9OrLater()) File(System.getProperty("java.home")) else File(System.getProperty("java.home")).parentFile ================================================ FILE: base/src/test/kotlin/testutils/LogUtils.kt ================================================ package testutils import org.apache.logging.log4j.core.LoggerContext import org.apache.logging.log4j.core.appender.OutputStreamAppender import java.io.ByteArrayOutputStream import java.util.UUID fun getLogOutputOf(closure: () -> Unit): String { val loggerOutput = ByteArrayOutputStream() val context = LoggerContext.getContext(false) val config = context.configuration val layout = config.rootLogger.appenders.values.first().layout val name = "Logger-${UUID.randomUUID()}" val appender = OutputStreamAppender.createAppender(layout, null, loggerOutput, name, false, true) appender.start() config.addAppender(appender) config.rootLogger.addAppender(appender, null, null) try { closure() } finally { config.rootLogger.removeAppender(name) } return loggerOutput.toString() } ================================================ FILE: base/src/test/kotlin/testutils/ProcessingFlagUtil.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package testutils import io.kotest.matchers.Matcher import io.kotest.matchers.MatcherResult import io.kotest.matchers.should import io.kotest.matchers.shouldNot import proguard.util.ProcessingFlags.DONT_OBFUSCATE import proguard.util.ProcessingFlags.DONT_OPTIMIZE import proguard.util.ProcessingFlags.DONT_PROCESS_KOTLIN_MODULE import proguard.util.ProcessingFlags.DONT_SHRINK fun hasFlag(flag: Int) = object : Matcher { override fun test(value: Int): MatcherResult = MatcherResult( (value and flag) != 0, { "Flag ${flag.asProcessingFlagString} should be set" }, { "Flag ${flag.asProcessingFlagString} should not be set" } ) } infix fun Int.shouldHaveFlag(flag: Int) = this should hasFlag(flag) infix fun Int.shouldNotHaveFlag(flag: Int) = this shouldNot hasFlag(flag) val Int.asProcessingFlagString: String get() = when (this) { DONT_OBFUSCATE -> "DONT_OBFUSCATE" DONT_SHRINK -> "DONT_SHRINK" DONT_OPTIMIZE -> "DONT_OPTIMIZE" DONT_PROCESS_KOTLIN_MODULE -> "DONT_PROCESS_KOTLIN_MODULE" else -> this.toString() } ================================================ FILE: base/src/test/kotlin/testutils/TestConfig.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package testutils import io.kotest.core.config.AbstractProjectConfig import io.kotest.core.extensions.Extension import io.kotest.core.filter.SpecFilter import io.kotest.core.filter.SpecFilterResult import io.kotest.core.filter.SpecFilterResult.Exclude import io.kotest.core.filter.SpecFilterResult.Include import kotlin.reflect.KClass import kotlin.reflect.full.findAnnotation @Suppress("UNUSED") object TestConfig : AbstractProjectConfig() { override fun extensions(): List { return listOf(RequiresJavaVersionAnnotationFilter()) } } class RequiresJavaVersionAnnotationFilter : SpecFilter { override fun filter(kclass: KClass<*>): SpecFilterResult = if (with(kclass.findAnnotation()) { (this == null || (currentJavaVersion >= this.from && currentJavaVersion <= this.to)) } ) Include else Exclude("Required Java version is not in range.") } @Target(AnnotationTarget.CLASS) annotation class RequiresJavaVersion(val from: Int, val to: Int = Int.MAX_VALUE) ================================================ FILE: bin/dprotect.sh ================================================ #!/bin/sh # # Start-up script for dProtect # # Note: when passing file names containing spaces to this script, # you'll have to add escaped quotes around them, e.g. # "\"/My Directory/My File.txt\"" # Account for possibly missing/basic readlink. # POSIX conformant (dash/ksh/zsh/bash). DPROTECT=`readlink -f "$0" 2>/dev/null` if test "$DPROTECT" = '' then DPROTECT=`readlink "$0" 2>/dev/null` if test "$DPROTECT" = '' then DPROTECT="$0" fi fi DPROTECT_HOME=`dirname "$DPROTECT"`/.. java -jar "$DPROTECT_HOME/lib/dprotect.jar" "$@" ================================================ FILE: bin/proguard.bat ================================================ @ECHO OFF REM Start-up script for ProGuard -- free class file shrinker, optimizer, REM obfuscator, and preverifier for Java bytecode. REM REM Note: when passing file names containing spaces to this script, REM you'll have to add escaped quotes around them, e.g. REM "\"C:/My Directory/My File.txt\"" IF EXIST "%PROGUARD_HOME%" GOTO home SET PROGUARD_HOME=%~dp0\.. :home java -jar "%PROGUARD_HOME%\lib\proguard.jar" %* ================================================ FILE: bin/proguard.sh ================================================ #!/bin/sh # # Start-up script for ProGuard -- free class file shrinker, optimizer, # obfuscator, and preverifier for Java bytecode. # # Note: when passing file names containing spaces to this script, # you'll have to add escaped quotes around them, e.g. # "\"/My Directory/My File.txt\"" # Account for possibly missing/basic readlink. # POSIX conformant (dash/ksh/zsh/bash). PROGUARD=`readlink -f "$0" 2>/dev/null` if test "$PROGUARD" = '' then PROGUARD=`readlink "$0" 2>/dev/null` if test "$PROGUARD" = '' then PROGUARD="$0" fi fi PROGUARD_HOME=`dirname "$PROGUARD"`/.. java -jar "$PROGUARD_HOME/lib/proguard.jar" "$@" ================================================ FILE: bin/proguardgui.bat ================================================ @ECHO OFF REM Start-up script for the GUI of ProGuard -- free class file shrinker, REM optimizer, obfuscator, and preverifier for Java bytecode. REM REM Note: when passing file names containing spaces to this script, REM you'll have to add escaped quotes around them, e.g. REM "\"C:/My Directory/My File.txt\"" IF EXIST "%PROGUARD_HOME%" GOTO home SET PROGUARD_HOME=%~dp0\.. :home java -jar "%PROGUARD_HOME%\lib\proguardgui.jar" %* ================================================ FILE: bin/proguardgui.sh ================================================ #!/bin/sh # # Start-up script for the GUI of ProGuard -- free class file shrinker, # optimizer, obfuscator, and preverifier for Java bytecode. # # Note: when passing file names containing spaces to this script, # you'll have to add escaped quotes around them, e.g. # "\"/My Directory/My File.txt\"" # Account for possibly missing/basic readlink. # POSIX conformant (dash/ksh/zsh/bash). PROGUARD=`readlink -f "$0" 2>/dev/null` if test "$PROGUARD" = '' then PROGUARD=`readlink "$0" 2>/dev/null` if test "$PROGUARD" = '' then PROGUARD="$0" fi fi PROGUARD_HOME=`dirname "$PROGUARD"`/.. # On Linux, Java 1.6.0_24 and higher hang when starting the GUI: # http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7027598 # We're using the -D option as a workaround. java -DsuppressSwingDropSupport=true -jar "$PROGUARD_HOME/lib/proguardgui.jar" "$@" ================================================ FILE: bin/retrace.bat ================================================ @ECHO OFF REM Start-up script for Retrace -- companion tool for ProGuard, free class file REM shrinker, optimizer, obfuscator, and preverifier for Java bytecode. REM REM Note: when passing file names containing spaces to this script, REM you'll have to add escaped quotes around them, e.g. REM "\"C:/My Directory/My File.txt\"" IF EXIST "%PROGUARD_HOME%" GOTO home SET PROGUARD_HOME=%~dp0\.. :home java -jar "%PROGUARD_HOME%\lib\retrace.jar" %* ================================================ FILE: bin/retrace.sh ================================================ #!/bin/sh # # Start-up script for Retrace -- companion tool for ProGuard, free class file # shrinker, optimizer, obfuscator, and preverifier for Java bytecode. # # Note: when passing file names containing spaces to this script, # you'll have to add escaped quotes around them, e.g. # "\"/My Directory/My File.txt\"" # Account for possibly missing/basic readlink. # POSIX conformant (dash/ksh/zsh/bash). PROGUARD=`readlink -f "$0" 2>/dev/null` if test "$PROGUARD" = '' then PROGUARD=`readlink "$0" 2>/dev/null` if test "$PROGUARD" = '' then PROGUARD="$0" fi fi PROGUARD_HOME=`dirname "$PROGUARD"`/.. java -jar "$PROGUARD_HOME/lib/retrace.jar" "$@" ================================================ FILE: build.gradle ================================================ plugins { id 'distribution' id 'io.github.gradle-nexus.publish-plugin' id 'signing' } allprojects { group = 're.obfuscator' version = dProtectVersion repositories { mavenLocal() maven { url = uri("https://maven.pkg.github.com/open-obfuscator/dProtect") } } } task buildDocumentation(type: Exec) { inputs.dir 'docs/md' inputs.file 'docs/mkdocs.yml' outputs.dir 'docs/html' executable 'sh' args '-c', "docker run --volume $rootDir/docs:/docs --rm squidfunk/mkdocs-material:5.2.2 build" } nexusPublishing { repositories { sonatype { username = findProperty('PROGUARD_STAGING_USERNAME') password = findProperty('PROGUARD_STAGING_PASSWORD') } } } // Add Maven repositories allprojects { Project project -> afterEvaluate { if (pluginManager.hasPlugin('maven-publish')) { configure(project) { publishing { repositories { maven { name = 'Github' url = uri('https://maven.pkg.github.com/open-obfuscator/dProtect') credentials { username = System.getenv('DPROTECT_GITHUB_USERNAME') password = System.getenv('DPROTECT_GITHUB_TOKEN') } } } } } } } } // Add publication allprojects { Project project -> afterEvaluate { if (pluginManager.hasPlugin('maven-publish')) { configure(project) { publishing { publications { create(project.name, MavenPublication) { pom { artifactId = "dprotect-$project.name" name = "$group:$artifactId" url = 'https://obfuscator.re/dprotect' licenses { license { name = 'GNU General Public License, Version 2' url = 'https://www.gnu.org/licenses/gpl-2.0.txt' distribution = 'repo' } } issueManagement { system = 'Github Tracker' url = 'https://github.com/open-obfuscator/dProtect/issues' } scm { url = 'https://github.com/open-obfuscator/dProtect.git' connection = 'scm:git:https://github.com/open-obfuscator/dProtect.git' } developers { developer { id = 'lafortune' name = 'Eric Lafortune' organization = 'Guardsquare' organizationUrl = 'https://www.guardsquare.com/' roles = ['Project Administrator', 'Developer'] } } } } } } } } } } // Configure default publication (all Java projects) allprojects { Project project -> afterEvaluate { if (pluginManager.hasPlugin('maven-publish') && pluginManager.hasPlugin('java')) { configure(project) { javadoc { options.addStringOption('Xdoclint:none', '-quiet') } java { toolchain { languageVersion.set(JavaLanguageVersion.of(8)) } withJavadocJar() withSourcesJar() } publishing { publications { getByName(project.name) { from components.java } } } } } } } // Configure signing allprojects { Project project -> afterEvaluate { if (pluginManager.hasPlugin('maven-publish')) { configure(project) { if (project.hasProperty('PROGUARD_SIGNING_KEY')) { // We use in-memory ascii-armored keys // See https://docs.gradle.org/current/userguide/signing_plugin.html#sec:in-memory-keys signing { String key = project.findProperty('PROGUARD_SIGNING_KEY') String password = project.findProperty('PROGUARD_SIGNING_PASSWORD') useInMemoryPgpKeys(key, password) sign publishing.publications.getByName(project.name) } } } } } } distributions { main { distributionBaseName.set('dprotect') contents { into('lib') { from tasks.getByPath(':dprotect:fatJar').outputs from tasks.getByPath(':proguard-app:fatJar').outputs from tasks.getByPath(':gradle:fatJar').outputs from tasks.getByPath(':gui:fatJar').outputs from tasks.getByPath(':retrace:fatJar').outputs from tasks.getByPath(':ant:fatJar').outputs } into('docs') { from('docs/md') { includeEmptyDirs = false include '**/*.md' } } from(rootDir) { include 'bin/' include 'examples/' exclude 'examples/*/build' exclude 'examples/*/.gradle' include 'LICENSE' include 'LICENSE_exception.md' } } } } distTar { compression = Compression.GZIP archiveExtension.set('tar.gz') } clean { delete file("$rootDir/lib") // TODO docker runs as root, so cannot clean the HTML yet // delete buildDocumentation.outputs } ================================================ FILE: docs/md/css/admonition.css ================================================ :root { --md-admonition-icon--android: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABAxJREFUWEetlz+oHFUUxn/fZqOIQbCJghaClagBbVJYzD6Fnc7KYGdjTDWvkGBhYWFn/0ZQCLaKFraZF3jspBBSKGIpSASLqIiFyiNmN++zuPfOzO7e3X0BP5iZ++ecc8+/e+4dsQUHs3Ik8XlVNG9uowOo2/IK8ANwqyoa76I/Neq2fKuelW8sjc2mj9ez6cMDmsfqtvxqnXs3tG2ybktVReO6nd4AfjS6DDwkwBgMgi+Qnq+K5kLdTkdVcXiyTeYqNirQLT6bfmnpUiA0INsIbA35pZ+wX6omh39vEJnFKDfYW17+inQJuAl8k9ZTsD2sLx0jfQZ+1vBX3ZZP52RuQlaBuPh3tp8A2C+aoiqaV2wDyFEHY4B3q6J5GzQTnIB/ycnchGwI6llZIq7btiQBR9hC2gs6eMCsu4iPsa9aGCOJa1Vx+E5O9iqyHkB8CCBpAcwNrxr2gHsSc9AcmCPmyGPgKmIuWEiaGy5n5WaQVwAugo/B8/gcSxyDF0AcIz33gOO+77kQdVs+t0H2dhy0061b8/+GDtryRezrku4CF6ui+aNuyzvBA4DB0lKy2GETyLHgqasMKS/OAa9XRXOrbssjmxeAD/YnzaesYCy4gXSekFlHwAXgSdD9ZdJePCwrlKD+fQZYHMymH9neA+4LfQKsKwA8QZA+As4P5kJ+aElw14obZFDzhbHC/jRIlngm7FifQWTPh3FPr1hcIQz0LKHkKYxDL8nOOMIKIQEjK9FnaWGUogihwoSeAssyrQ144Al3T9R1ZYkNRi9hLJRkrgU2uVgrfRzdnQkBkmPRipym80IGK3Vgt8YJm5Mwwt1rfW6AsWNsJIVgD2BbSXtJdncAGinMB8reRsWMiUGN016LZ8Koy5chHLdc5/vBN7aXGLTypFweUm1wwRi6pB1kQYpdz+VBOYrXgTWZQe9+NEnuD9F1jIDfCCqfAL/3U9GU4FMrCDAhUA4eklEal9W1g2zDbQyS7q8U0w4jw2vAHfBtwok3dKNwd/53To59sBX3t8Byagdjz+0XzfuII/CfhiuZ9fNa1bPSyLbZmITL6McGAdirJs2MHRhnR4OUlX3urg4EQx2LZ4Y5pE825qvIK2CC80PFGZiVOk7dqOoSs4cx3IVNN6L3AGEtupAjx/RbCC2EFsQkHSy1AAnxfVU0N7OyV5DNgSHqtvwWeBniDRE/Wk0OjwHqWfkz+CmkM8FbeqSaNHfj3KiaNDv/ETZdyYbolAzlXWfrtoyu70xPPu/mTrM4nEaBYbid/ocS+hM7poMf9L9wtwKxgKTXUnqlsyCeGYnsQbBbgXDrhf44OakmwUpL47A1u1j8u86+HadRoAofjWx9XRXNP2lCUGGfDU2u7U+ae3kRm/Ef38/ZmrIoJ+YAAAAASUVORK5CYII='); } .md-typeset .admonition.android, .md-typeset details.android { border-color: rgb(43, 155, 70); } .md-typeset .android > .admonition-title, .md-typeset .android > summary { background-color: rgba(43, 155, 70, 0.1); border-color: rgb(43, 155, 70); } .md-typeset .android > .admonition-title::before, .md-typeset .android > summary::before { background-color: rgb(43, 155, 70); -webkit-mask-image: var(--md-admonition-icon--android); mask-image: var(--md-admonition-icon--android); -webkit-mask-size: 100%; mask-size: 100%; } ================================================ FILE: docs/md/css/extra.css ================================================ @charset "iso-8859-1"; /* Settings for Mkdocs Material. */ :root { /* Default color shades * / --md-default-fg-color: ...; --md-default-fg-color--light: ...; --md-default-fg-color--lighter: ...; --md-default-fg-color--lightest: ...; --md-default-bg-color: ...; --md-default-bg-color--light: ...; --md-default-bg-color--lighter: ...; --md-default-bg-color--lightest: ...; /* Primary color shades */ --md-primary-fg-color: #004F91; /*--md-primary-fg-color--light: ...; --md-primary-fg-color--dark: ...; --md-primary-bg-color: ...; --md-primary-bg-color--light: ...; /* Accent color shades */ --md-accent-fg-color: #004F91; /*--md-accent-fg-color--transparent: ...; --md-accent-bg-color: ...; --md-accent-bg-color--light: ...; /* Code block color shades * / --md-code-bg-color: ...; --md-code-fg-color: ...; */ } /* Settings for diagrams. */ .center { text-align: center; } div.diagram { display: inline-block; position: relative; } .distributed { display: flex; justify-content: space-between; } .title { text-align: center; font-weight: bold; } .row, .col { display: block; position: relative; } .row { padding-top: 5px; padding-bottom: 5px; } div.row > div { float: left; margin-top: 0px; margin-bottom: 0px; } .col { padding-left: 10px; padding-right: 10px; } div.col > div { margin-left: 0px; margin-right: 0px; } div.row:after { content: ""; display: table; clear: both; } .overlap { position: absolute; } .label { position: absolute; top: 20px; left: 100px; text-align: left; white-space: nowrap; color: black; padding: 3px 5px 3px 5px; background: yellow; border: 1px solid black; } .frame { border: 1px solid #446600; border-radius: 4px; margin: -1px; } .lightgreen { background: #A4C639; border: 1px solid #84A619; } .green { background: #84A619; border: 1px solid #648600; } .darkgreen { background: #648600; border: 1px solid #446600; } .blackgreen { background: #446600; border: 1px solid #244600; } .box { position: relative; width: 110px; height: 110px; display: flex; justify-content: center; text-align: center; align-items: center; color: #FFFFFF; border-radius: 4px; margin: 5px 10px 10px 10px; } .arrow { position: relative; display: flex; justify-content: center; text-align: center; align-items: center; color: #ffffff; background: #0069b4; border: 1px solid #004f91; border-radius: 4px; } .arrow:after, .arrow:before { height: 0; width: 0; position: absolute; content: ""; border: solid transparent; border-color: #00000000; pointer-events: none; } .right { top: 40px; width: 100px; height: 50px; margin: 5px 30px 5px 5px; } .right:after, .right:before { left: 100%; top: 50%; } .right:after { border-width: 33px; margin-top: -33px; border-left-color: #0069b4; } .right:before { border-width: 35px; margin-top: -35px; border-left-color: #004f91; } .left { top: 40px; width: 100px; height: 50px; margin: 5px 5px 5px 30px; } .left:after, .left:before { right: 100%; top: 50%; } .left:after { border-width: 33px; margin-top: -33px; border-right-color: #0069b4; } .left:before { border-width: 35px; margin-top: -35px; border-right-color: #004f91; } .down { left: 35px; width: 60px; height: 130px; margin: 0px 5px 30px 5px; } .down:after, .down:before { top: 100%; left: 50%; } .down:after { border-width: 38px; margin-left: -38px; border-top-color: #0069b4; } .down:before { border-width: 40px; margin-left: -40px; border-top-color: #004f91; } p { text-align: justify; text-justify: inter-word; } ================================================ FILE: docs/md/downloads.md ================================================ **ProGuard** is distributed under the terms of the GNU General Public License. Please consult the [license page](license.md) for more details. ProGuard is written in Java, so it requires a Java Runtime Environment (JRE 1.8 or higher). You can download ProGuard in various forms: - [Pre-built artifacts](https://search.maven.org/search?q=g:com.guardsquare) at Maven Central - A [Git repository of the source code](https://github.com/Guardsquare/proguard) at Github - The [complete ProGuard manual](https://www.guardsquare.com/proguard) at Guardsquare You can find major releases, minor releases with important bug fixes, and beta releases with the latest new features and any less urgent bug fixes. If you're still working with an older version of ProGuard, check out the [release notes](releasenotes.md), to see if you're missing something essential. Unless noted otherwise, ProGuard remains compatible across versions, so don't be afraid to update. ================================================ FILE: docs/md/index.md ================================================ **ProGuard** is a free Java class file shrinker, optimizer, obfuscator, and preverifier. It detects and removes unused classes, fields, methods, and attributes. It optimizes bytecode and removes unused instructions. It renames the remaining classes, fields, and methods using short meaningless names. The resulting applications and libraries are smaller, faster, and a bit better hardened against reverse engineering. Typical applications: - Reducing the size of apps for faster downloads, shorter startup times, and smaller memory footprints. - Optimizing code for better performance on mobile devices. **ProGuard**'s main advantage compared to other Java obfuscators is probably its compact template-based configuration. A few intuitive command line options or a simple configuration file are usually sufficient. The user manual explains all available options and shows examples of this powerful configuration style. **ProGuard** is fast. It only takes seconds to process programs and libraries of several megabytes. The results section presents actual figures for a number of applications. **ProGuard** is a command-line tool with an optional graphical user interface. It also comes with plugins for Ant, for Gradle, and for the JME Wireless Toolkit. It is already part of Google's Android SDK, where it can be enabled with a simple flag. **ProGuard** provides basic protection against reverse engineering and tampering, with basic name obfuscation. [**DexGuard**](http://www.guardsquare.com/dexguard), its specialized commercial extension for Android, focuses further on the protection of apps, additionally optimizing, obfuscating and encrypting strings, classes, resources, resource files, asset files, and native libraries. Professional developers should definitely consider it for security-sensitive apps. ================================================ FILE: docs/md/manual/FAQ.md ================================================ ## What is shrinking? {: #shrinking} Java source code (.java files) is typically compiled to bytecode (.class files). Bytecode is more compact than Java source code, but it may still contain a lot of unused code, especially if it includes program libraries. Shrinking programs such as **ProGuard** can analyze bytecode and remove unused classes, fields, and methods. The program remains functionally equivalent, including the information given in exception stack traces. ## What is name obfuscation? {: #obfuscation} By default, compiled bytecode still contains a lot of debugging information: source file names, line numbers, field names, method names, argument names, variable names, etc. This information makes it straightforward to decompile the bytecode and reverse-engineer entire programs. Sometimes, this is not desirable. Shrinkers such as **ProGuard** can remove the debugging information and replace all names by meaningless character sequences, making apps smaller. The program remains functionally equivalent, except for the class names, method names, and line numbers given in exception stack traces. ## What is preverification? {: #preverification} When loading class files, the class loader performs some sophisticated verification of the byte code. This analysis makes sure the code can't accidentally or intentionally break out of the sandbox of the virtual machine. Java Micro Edition and Java 6 introduced split verification. This means that the JME preverifier and the Java 6 compiler add preverification information to the class files (StackMap and StackMapTable attributes, respectively), in order to simplify the actual verification step for the class loader. Class files can then be loaded faster and in a more memory-efficient way. **ProGuard** automatically preverifies the code that it processes. ## What kind of optimizations does ProGuard support? {: #optimization} Apart from removing unused classes, fields, and methods in the shrinking step, **ProGuard** can also perform optimizations at the bytecode level, inside and across methods. Thanks to techniques like control flow analysis, data flow analysis, partial evaluation, static single assignment, global value numbering, and liveness analysis, **ProGuard** can: - Evaluate constant expressions. - Remove unnecessary field accesses and method calls. - Remove unnecessary branches. - Remove unnecessary comparisons and instanceof tests. - Remove unused code blocks. - Merge identical code blocks. - Reduce variable allocation. - Remove write-only fields and unused method parameters. - Inline constant fields, method parameters, and return values. - Inline methods that are short or only called once. - Simplify tail recursion calls. - Merge classes and interfaces. - Make methods private, static, and final when possible. - Make classes static and final when possible. - Replace interfaces that have single implementations. - Perform over 200 peephole optimizations, like replacing `"The answer is "+42` by `"The answer is 42"`. - Optionally remove logging code. The positive effects of these optimizations will depend on your code and on the virtual machine on which the code is executed. Simple virtual machines may benefit more than advanced virtual machines with sophisticated JIT compilers. At the very least, your bytecode may become a bit smaller. ## Can I use ProGuard to process my commercial application? {: #commercial} Yes, you can. **ProGuard** itself is distributed under the GPL, but this doesn't affect the programs that you process. Your code remains yours, and its license can remain the same. ## Does ProGuard work with Java 2, 5,..., 19? {: #jdk1.4} Yes, **ProGuard** supports all JDKs from 1.1 up to and including 19. Java 2 introduced some small differences in the class file format. Java 5 added attributes for generics and for annotations. Java 6 introduced optional preverification attributes. Java 7 made preverification obligatory and introduced support for dynamic languages. Java 8 added more attributes and default methods. Java 9 added support for modules. Java 11 added dynamic constants and nest-based access control. Java 14 added records. Java 15 added sealed classes. **ProGuard** handles all versions correctly. ## Does ProGuard work with Java Micro Edition? {: #jme} Yes. **ProGuard** itself runs in Java Standard Edition, but you can freely specify the run-time environment at which your programs are targeted, including Java Micro Edition. **ProGuard** then also performs the required preverification, producing more compact results than the traditional external preverifier. ## Does ProGuard Support Android Apps? The **ProGuard Gradle Plugin** is compatible with Android Gradle Plugin (AGP) versions 4.x - 7.x. The **ProGuard** [keep rules configuration format](configuration/usage.md) is also supported by R8 (the default Android shrinker), so you can use your R8, ProGuard and DexGuard keep rules interchangeably. See [Gradle Plugin setup](setup/gradleplugin.md) page for more information. ## Does ProGuard have support for Ant? {: #ant} Yes. **ProGuard** provides an Ant task, so that it integrates seamlessly into your Ant build process. You can still use configurations in **ProGuard**'s own readable format. Alternatively, if you prefer XML, you can specify the equivalent XML configuration. See [Ant setup](setup/ant.md) page for more information. ## Does ProGuard have support for Java/Kotlin Gradle projects? {: #gradle} Yes. **ProGuard** also provides a Gradle task, so that it integrates into your Gradle build process. You can specify configurations in **ProGuard**'s own format or embedded in the Groovy configuration. See [Gradle setup](setup/gradle.md) page for more information. ## Does ProGuard have support for Maven? {: #maven} While we don't officially provide a Maven integration and we cannot provide support there are solutions available, their offered functionality is not guaranteed by Guardsquare. Some open-source implementations: - [https://github.com/wvengen/proguard-maven-plugin](https://github.com/wvengen/proguard-maven-plugin) - [https://github.com/dingxin/proguard-maven-plugin](https://github.com/dingxin/proguard-maven-plugin) ## Does ProGuard come with a GUI? {: #gui} Yes. First of all, **ProGuard** is perfectly usable as a command-line tool that can easily be integrated into any automatic build process. For casual users, there's also a graphical user interface that simplifies creating, loading, editing, executing, and saving ProGuard configurations. ## Does ProGuard handle `Class.forName` calls? {: #forname} Yes. **ProGuard** automatically handles constructs like `Class.forName("SomeClass")` and `SomeClass.class`. The referenced classes are preserved in the shrinking phase, and the string arguments are properly replaced in the obfuscation phase. With variable string arguments, it's generally not possible to determine their possible values. They might be read from a configuration file, for instance. However, **ProGuard** will note a number of constructs like "`(SomeClass)Class.forName(variable).newInstance()`". These might be an indication that the class or interface `SomeClass` and/or its implementations may need to be preserved. The developer can adapt his configuration accordingly. ## Does ProGuard handle resource files? {: #resource} Yes. **ProGuard** copies all non-class resource files, optionally adapting their names and their contents to the obfuscation that has been applied. ## Does ProGuard encrypt string constants? {: #encrypt} No. String encryption in program code has to be perfectly reversible by definition, so it only improves the obfuscation level. It increases the footprint of the code. However, by popular demand, **ProGuard**'s closed-source sibling for Android, [**DexGuard**](http://www.guardsquare.com/dexguard), does provide string encryption, along with more protection techniques against static and dynamic analysis. ## Does ProGuard perform control flow obfuscation? No. Control flow obfuscation injects additional branches into the bytecode, in an attempt to fool decompilers. **ProGuard** does not do this, except to some extent in its optimization techniques. **ProGuard**'s closed-source sibling for Android, [**DexGuard**](http://www.guardsquare.com/dexguard), does offer control flow obfuscation, as one of the many additional techniques to harden Android apps. ## Does ProGuard support incremental obfuscation? {: #incremental} Yes. This feature allows you to specify a previous obfuscation mapping file in a new obfuscation step, in order to produce add-ons or patches for obfuscated code. ## Can ProGuard obfuscate using reserved keywords? {: #keywords} Yes. You can specify your own obfuscation dictionary, such as a list of reserved key words, identifiers with foreign characters, random source files, or a text by Shakespeare. Note that this hardly improves the obfuscation. Decent decompilers can automatically replace reserved keywords, and the effect can be undone fairly easily, by obfuscating again with simpler names. ## Can ProGuard reconstruct obfuscated stack traces? {: #stacktrace} Yes. **ProGuard** comes with a companion tool, **ReTrace**, that can 'de-obfuscate' stack traces produced by obfuscated applications. The reconstruction is based on the mapping file that **ProGuard** can write out. If line numbers have been obfuscated away, a list of alternative method names is presented for each obfuscated method name that has an ambiguous reverse mapping. Please refer to the [ProGuard User Manual](manual/index.md) for more details. ## How is DexGuard different from ProGuard? [**DexGuard**](http://www.guardsquare.com/dexguard) is a commercial extension of **ProGuard**: - **DexGuard** is specialized for Android applications and libraries: it optimizes and obfuscates not just the bytecode, but also the manifest file, resources, resource files, asset files, and native libraries. - **DexGuard** focuses on making apps self-defending against reverse engineering and tampering. **DexGuard**'s techniques for obfuscation, encryption, and detection are a lot stronger than **ProGuard**'s basic name obfuscation. - **DexGuard** is backward compatible with **ProGuard**: it reads the same configuration. It already comes with tuned configuration for the Android runtime and for common Android libraries. ================================================ FILE: docs/md/manual/building.md ================================================ # Building ProGuard !!! info **ProGuard** is distributed under the terms of the GNU General Public License. Please consult the [license page](license/license.md) for more details. Building ProGuard is easy - you'll need: * a Java 8 JDK installed * a clone of the [ProGuard](https://github.com/Guardsquare/proguard.git) repository You can then execute a composite build with the following Gradle command: === "Linux/macOS" ```bash ./gradlew assemble ``` === "Windows" ```bash gradlew assemble ``` The artifacts will be generated in the `lib` directory. You can then execute ProGuard using the scripts in `bin`, for example: === "Linux/macOS" ```bash bin/proguard.sh ``` === "Windows" ```bash bin\proguard.bat ``` ## Publish to Maven local You can publish the artifacts to your local Maven cache (something like `~/.m2/`): === "Linux/macOS" ```bash ./gradlew publishToMavenLocal ``` === "Windows" ```bash gradlew publishToMavenLocal ``` ## Building a release distribution You can build tar and zip archives with the binaries and documentation: === "Linux/macOS" ```bash ./gradlew distTar distZip ``` === "Windows" ```bash gradlew distTar distZip ``` ================================================ FILE: docs/md/manual/configuration/attributes.md ================================================ Class files essentially define classes, their fields, and their methods. A lot of essential and non-essential data are attached to these classes, fields, and methods as *attributes*. For instance, attributes can contain bytecode, source file names, line number tables, etc. ProGuard's obfuscation step removes attributes that are generally not necessary for executing the code. With the [`-keepattributes`](usage.md#keepattributes) option, you can specify a filter for attributes that you do want to keep, for instance if your code accesses them through reflection, or if you want to preserve some compilation or debugging information. The filter works like any [filter](usage.md#filters) in ProGuard. The following wildcards are supported: | Wildcard | Meaning |-----|---------------------------------------------------- | `?` | matches any single character in an attribute name. | `*` | matches any part of an attribute name. An attribute name that is preceded by an exclamation mark '**!**' is *excluded* from further attempts to match with *subsequent* attribute names in the filter. Make sure to specify filters correctly, since they are not checked for potential typos. For example, the following setting preserves the optional attributes that are typically necessary when processing code that is intended to be used as a library: ```proguard -keepattributes Exceptions,InnerClasses,Signature,Deprecated, SourceFile,LineNumberTable,*Annotation*,EnclosingMethod ``` The Java bytecode specifications currently specify the following list of attributes. ## Optional attributes ProGuard's obfuscation step by default discards the following optional attributes. You can keep them with the [`-keepattributes`](usage.md#keepattributes) option. `SourceFile` : Specifies the name of the source file from which the class file was compiled. If present, this name is reported in stack traces. `SourceDir`

(J++ extension)
: Specifies the name of the source directory from which the class file was compiled. `Record`
(Java 14 or higher)
: Specifies the components of a record class. Code may access this information by reflection. `InnerClasses` : Specifies the relationship between a class and its inner classes and outer classes. Other than this and the naming convention with a '\$' separator between the names of inner classes and outer classes, inner classes are just like ordinary classes. Compilers may need this information to find classes referenced in a compiled library. Code may access this information by reflection, for instance to derive the simple name of the class. `PermittedSubclasses`
(Java 15 or higher)
: Specifies the allowed extensions or implementations of sealed classes or interfaces. `EnclosingMethod`
(Java 5 or higher)
: Specifies the method in which the class was defined. Compilers may need this information to find classes referenced in a compiled library. Code may access this information by reflection, for instance to derive the simple name of the class. `Deprecated` : Indicates that the class, field, or method is deprecated. `Synthetic` : Indicates that the class, field, or method was generated by the compiler. `Signature`
(Java 5 or higher)
: Specifies the generic signature of the class, field, or method. Compilers may need this information to properly compile classes that use generic types from compiled libraries. Code may access this signature by reflection. `MethodParameters`
(Java 8 or higher)
: Specifies the names and access flags of the parameters of the method. Code may access this information by reflection. `Exceptions` : Specifies the exceptions that a method may throw. Compilers may use this information to enforce catching them. `LineNumberTable` : Specifies the line numbers of the method. If present, these line numbers are reported in stack traces. `LocalVariableTable` : Specifies the names and types of local variables of the method. If present, some IDEs may use this information for helping with auto-completion. `LocalVariableTypeTable`
(Java 5 or higher)
: Specifies the names and generic types of local variables of the method. If present, some IDEs may use this information for helping with auto-completion. `RuntimeVisibleAnnotations`
(Java 5 or higher)
: Specifies the annotations that are visible at run-time, for classes, fields, and methods. Compilers and annotation processors may use these annotations. Code may access them by reflection. `RuntimeInvisibleAnnotations`
(Java 5 or higher)
: Specifies the annotations that are visible at compile-time, for classes, fields, and methods. Compilers and annotation processors may use these annotations. `RuntimeVisibleParameterAnnotations`
(Java 5 or higher)
: Specifies the annotations that are visible at run-time, for method parameters. Compilers and annotation processors may use these annotations. Code may access them by reflection. `RuntimeInvisibleParameterAnnotations`
(Java 5 or higher)
: Specifies the annotations that are visible at compile-time, for method parameters. Compilers and annotation processors may use these annotations. `RuntimeVisibleTypeAnnotations`
(Java 8 or higher)
: Specifies the annotations that are visible at run-time, for generic types, instructions, etc. Compilers and annotation processors may use these annotations. Code may access them by reflection. `RuntimeInvisibleTypeAnnotations`
(Java 8 or higher)
: Specifies the annotations that are visible at compile-time, for generic types, instructions, etc. Compilers and annotation processors may use these annotations. `AnnotationDefault`
(Java 5 or higher)
: Specifies a default value for an annotation. ## Essential attributes ProGuard automatically keeps the following essential attributes, processing them as necessary. We're listing them for the sake of completeness: `ConstantValue` : Specifies a constant integer, float, class, string, etc. `Code` : Specifies the actual bytecode of a method. `StackMap`
(Java Micro Edition)
: Provides preverification information. The Java Virtual Machine can use this information to speed up the verification step when loading a class. `StackMapTable`
(Java 6 or higher)
: Provides preverification information. The Java Virtual Machine can use this information to speed up the verification step when loading a class. `BootstrapMethods`
(Java 7 or higher)
: Specifies the methods to bootstrap dynamic method invocations. `Module`
(Java 9 or higher)
: Specifies the dependencies of a _module_. `ModuleMainClass`
(Java 9 or higher)
: Specifies the main class of a _module_. `ModulePackages`
(Java 9 or higher)
: Specifies the packages of a _module_. `NestHost`
(Java 11 or higher)
: Specifies the host class of a _nest_, for example an outer class. `NestMembers`
(Java 11 or higher)
: Specifies the members of a _nest_, for example the inner classes. ================================================ FILE: docs/md/manual/configuration/examples.md ================================================ You can find some sample configuration files in the `examples` directory of the ProGuard distribution. ## Processing different types of applications {: #applicationtypes} ### A typical application {: #application} To shrink, optimize, and obfuscate a simple Java application, you typically create a configuration file like `myconfig.pro`, which you can then use with ```sh bin/proguard @myconfig.pro ``` The configuration file specifies the input, the output, and the entry points of the application: ```proguard -injars myapplication.jar -outjars myapplication_out.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -printmapping myapplication.map -keep public class com.example.MyMain { public static void main(java.lang.String[]); } ``` Note the use of the `` system property. ProGuard automatically replaces it when parsing the file. In this example, the library jar is the base Java runtime module, minus some unwanted files. For Java 8 or older, the Java runtime jar would be `/lib/rt.jar` instead. You may need additional modules or jars if your application depends on them. The [`-keep`](usage.md#keep) option specifies the entry point of the application that has to be preserved. The access modifiers `public` and `static` are not really required in this case, since we know a priori that the specified class and method have the proper access flags. It just looks more familiar this way. Note that all type names are fully specified: `com.example.MyMain` and `java.lang.String[]`. You can refine your keep rules using the ProGuard Playground which visualizes how your keep rules match the entities in your app. We're writing out an obfuscation mapping file with [`-printmapping`](usage.md#printmapping), for de-obfuscating any stack traces later on, or for incremental obfuscation of extensions. We can further improve the results with a few additional options: ```proguard -optimizationpasses 3 -overloadaggressively -repackageclasses '' -allowaccessmodification ``` These options are not required; they just shave off some extra bytes from the output jar, by performing up to 3 optimization passes, and by aggressively obfuscating class members and [package names](#repackaging). In general, you might need a few additional options for processing [native methods](#native), [callback methods](#callback), [enumerations](#enumerations), [serializable classes](#serializable), [bean classes](#beans), [annotations](#annotations), and [resource files](#resourcefiles). ### A typical applet {: #applet} These options shrink, optimize, and obfuscate the applet `com.example.MyApplet`: ```proguard -injars in.jar -outjars out.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -keep public class com.example.MyApplet ``` The typical applet methods will be preserved automatically, since `com.example.MyApplet` is an extension of the `Applet` class in the library `rt.jar`. If applicable, you should add options for processing [native methods](#native), [callback methods](#callback), [enumerations](#enumerations), [serializable classes](#serializable), [bean classes](#beans), [annotations](#annotations), and [resource files](#resourcefiles). ### A typical midlet {: #midlet} These options shrink, optimize, obfuscate, and preverify the midlet `com.example.MyMIDlet`: ```proguard -injars in.jar -outjars out.jar -libraryjars /usr/local/java/wtk2.5.2/lib/midpapi20.jar -libraryjars /usr/local/java/wtk2.5.2/lib/cldcapi11.jar -overloadaggressively -repackageclasses '' -allowaccessmodification -microedition -keep public class com.example.MyMIDlet ``` Note how we're now targeting the Java Micro Edition run-time environment of `midpapi20.jar` and `cldcapi11.jar`, instead of the Java Standard Edition run-time environment `rt.jar`. You can target other JME environments by picking the appropriate jars. The typical midlet methods will be preserved automatically, since `com.example.MyMIDlet` is an extension of the `MIDlet` class in the library `midpapi20.jar`. The [`-microedition`](usage.md#microedition) option makes sure the class files are preverified for Java Micro Edition, producing compact `StackMap` attributes. It is no longer necessary to run an external preverifier. Be careful if you do use the external `preverify` tool on a platform with a case-insensitive filing system, such as Windows. Because this tool unpacks your processed jars, you should then use ProGuard's [`-dontusemixedcaseclassnames`](usage.md#dontusemixedcaseclassnames) option. If applicable, you should add options for processing [native methods](#native) and [resource files](#resourcefiles). Note that you will still have to adapt the midlet jar size in the corresponding jad file; ProGuard doesn't do that for you. ### A typical Java Card applet {: #jcapplet} These options shrink, optimize, and obfuscate the Java Card applet `com.example.MyApplet`: ```proguard -injars in.jar -outjars out.jar -libraryjars /usr/local/java/javacard2.2.2/lib/api.jar -dontwarn java.lang.Class -overloadaggressively -repackageclasses '' -allowaccessmodification -keep public class com.example.MyApplet ``` The configuration is very similar to the configuration for midlets, except that it now targets the Java Card run-time environment. This environment doesn't have java.lang.Class, so we're telling ProGuard not to worry about it. ### A typical xlet {: #xlet} These options shrink, optimize, and obfuscate the xlet `com.example.MyXlet`: ```proguard -injars in.jar -outjars out.jar -libraryjars /usr/local/java/jtv1.1/javatv.jar -libraryjars /usr/local/java/cdc1.1/lib/cdc.jar -libraryjars /usr/local/java/cdc1.1/lib/btclasses.zip -overloadaggressively -repackageclasses '' -allowaccessmodification -keep public class com.example.MyXlet ``` The configuration is very similar to the configuration for midlets, except that it now targets the CDC run-time environment with the Java TV API. ### A typical library {: #library} These options shrink, optimize, and obfuscate an entire library, keeping all public and protected classes and class members, native method names, and serialization code. The processed version of the library can then still be used as such, for developing code based on its public API. ```proguard -injars in.jar -outjars out.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -printmapping out.map -keep public class * { public protected *; } -keepparameternames -renamesourcefileattribute SourceFile -keepattributes Signature,Exceptions,*Annotation*, InnerClasses,PermittedSubclasses,EnclosingMethod, Deprecated,SourceFile,LineNumberTable -keepclasseswithmembernames,includedescriptorclasses class * { native ; } -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } ``` This configuration should preserve everything a developers ever wants to access in the library. Only if there are any other non-public classes or methods that are invoked dynamically, they should be specified using additional [`-keep`](usage.md#keep) options. The "Signature" attribute is required to be able to access generic types. The "Exceptions" attribute has to be preserved, so the compiler knows which exceptions methods may throw. The various "\*Annotations\*" attributes contain any annotations, which developers might need to access through reflection. The "InnerClasses" attribute (or more precisely, its source name part) has to be preserved too, for any inner classes that can be referenced from outside the library. The `javac` compiler would be unable to find the inner classes otherwise. The "PermittedSubclasses" attribute defines sealed classes, which developers can't extend further. The "EnclosingMethod" attribute marks classes that are defined inside methods. The "Deprecated" attribute marks any deprecated classes, fields, or methods, which may be useful for developers to know. The [`-keepparameternames`](usage.md#keepparameternames) option keeps the parameter names in the "LocalVariableTable" and "LocalVariableTypeTable" attributes of public library methods. Some IDEs can present these names to the developers who use the library. Finally, we're keeping the "Deprecated" attribute and the attributes for producing [useful stack traces](#stacktrace). We've also added some options for for processing [native methods](#native), [enumerations](#enumerations), [serializable classes](#serializable), and [annotations](#annotations), which are all discussed in their respective examples. ### All possible applications in the input jars {: #applications} These options shrink, optimize, and obfuscate all public applications in `in.jar`: ```proguard -injars in.jar -outjars out.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -printseeds -keepclasseswithmembers public class * { public static void main(java.lang.String[]); } ``` Note the use of [`-keepclasseswithmembers`](usage.md#keepclasseswithmembers). We don't want to preserve all classes, just all classes that have main methods, _and_ those methods. The [`-printseeds`](usage.md#printseeds) option prints out which classes exactly will be preserved, so we know for sure we're getting what we want. If applicable, you should add options for processing [native methods](#native), [callback methods](#callback), [enumerations](#enumerations), [serializable classes](#serializable), [bean classes](#beans), [annotations](#annotations), and [resource files](#resourcefiles). ### All possible applets in the input jars {: #applets} These options shrink, optimize, and obfuscate all public applets in `in.jar`: ```proguard -injars in.jar -outjars out.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -libraryjars /jmods/java.desktop.jmod(!**.jar;!module-info.class) -printseeds -keep public class * extends java.applet.Applet ``` We're simply keeping all classes that extend the `Applet` class. Again, the [`-printseeds`](usage.md#printseeds) option prints out which applets exactly will be preserved. If applicable, you should add options for processing [native methods](#native), [callback methods](#callback), [enumerations](#enumerations), [serializable classes](#serializable), [bean classes](#beans), [annotations](#annotations), and [resource files](#resourcefiles). ### All possible midlets in the input jars {: #midlets} These options shrink, optimize, obfuscate, and preverify all public midlets in `in.jar`: ```proguard -injars in.jar -outjars out.jar -libraryjars /usr/local/java/wtk2.5.2/lib/midpapi20.jar -libraryjars /usr/local/java/wtk2.5.2/lib/cldcapi11.jar -overloadaggressively -repackageclasses '' -allowaccessmodification -microedition -printseeds -keep public class * extends javax.microedition.midlet.MIDlet ``` We're simply keeping all classes that extend the `MIDlet` class. The [`-microedition`](usage.md#microedition) option makes sure the class files are preverified for Java Micro Edition, producing compact `StackMap` attributes. It is no longer necessary to run an external preverifier. Be careful if you do use the external `preverify` tool on a platform with a case-insensitive filing system, such as Windows. Because this tool unpacks your processed jars, you should then use ProGuard's [`-dontusemixedcaseclassnames`](usage.md#dontusemixedcaseclassnames) option. The [`-printseeds`](usage.md#printseeds) option prints out which midlets exactly will be preserved. If applicable, you should add options for processing [native methods](#native) and [resource files](#resourcefiles). Note that you will still have to adapt the midlet jar size in the corresponding jad file; ProGuard doesn't do that for you. ### All possible Java Card applets in the input jars {: #jcapplets} These options shrink, optimize, and obfuscate all public Java Card applets in `in.jar`: ```proguard -injars in.jar -outjars out.jar -libraryjars /usr/local/java/javacard2.2.2/lib/api.jar -dontwarn java.lang.Class -overloadaggressively -repackageclasses '' -allowaccessmodification -printseeds -keep public class * implements javacard.framework.Applet ``` We're simply keeping all classes that implement the `Applet` interface. The [`-printseeds`](usage.md#printseeds) option prints out which applets exactly will be preserved. ### All possible xlets in the input jars {: #xlets} These options shrink, optimize, and obfuscate all public xlets in `in.jar`: ```proguard -injars in.jar -outjars out.jar -libraryjars /usr/local/java/jtv1.1/javatv.jar -libraryjars /usr/local/java/cdc1.1/lib/cdc.jar -libraryjars /usr/local/java/cdc1.1/lib/btclasses.zip -overloadaggressively -repackageclasses '' -allowaccessmodification -printseeds -keep public class * implements javax.tv.xlet.Xlet ``` We're simply keeping all classes that implement the `Xlet` interface. The [`-printseeds`](usage.md#printseeds) option prints out which xlets exactly will be preserved. ### All possible servlets in the input jars {: #servlets} These options shrink, optimize, and obfuscate all public servlets in `in.jar`: ```proguard -injars in.jar -outjars out.jar -libraryjars /lib/rt.jar -libraryjars /usr/local/java/servlet/servlet.jar -printseeds -keep public class * implements javax.servlet.Servlet ``` Keeping all servlets is very similar to keeping all applets. The servlet API is not part of the standard run-time jar, so we're specifying it as a library. Don't forget to use the right path name. We're then keeping all classes that implement the `Servlet` interface. We're using the `implements` keyword because it looks more familiar in this context, but it is equivalent to `extends`, as far as ProGuard is concerned. The [`-printseeds`](usage.md#printseeds) option prints out which servlets exactly will be preserved. If applicable, you should add options for processing [native methods](#native), [callback methods](#callback), [enumerations](#enumerations), [serializable classes](#serializable), [bean classes](#beans), [annotations](#annotations), and [resource files](#resourcefiles). ### Scala applications with the Scala runtime {: #scala} These options shrink, optimize, and obfuscate all public Scala applications in `in.jar`: ```proguard -injars in.jar -injars /usr/local/java/scala-2.9.1/lib/scala-library.jar -outjars out.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -dontwarn scala.** -keepclasseswithmembers public class * { public static void main(java.lang.String[]); } -keep class * implements org.xml.sax.EntityResolver -keepclassmembers class * { ** MODULE$; } -keepclassmembernames class scala.concurrent.forkjoin.ForkJoinPool { long eventCount; int workerCounts; int runControl; scala.concurrent.forkjoin.ForkJoinPool$WaitQueueNode syncStack; scala.concurrent.forkjoin.ForkJoinPool$WaitQueueNode spareStack; } -keepclassmembernames class scala.concurrent.forkjoin.ForkJoinWorkerThread { int base; int sp; int runState; } -keepclassmembernames class scala.concurrent.forkjoin.ForkJoinTask { int status; } -keepclassmembernames class scala.concurrent.forkjoin.LinkedTransferQueue { scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference head; scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference tail; scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference cleanMe; } ``` The configuration is essentially the same as for [processing applications](#applications), because Scala is compiled to ordinary Java bytecode. However, the example processes the Scala runtime library as well. The processed jar can be an order of magnitude smaller and a few times faster than the original code (for the Scala code examples, for instance). The [`-dontwarn`](usage.md#dontwarn) option tells ProGuard not to complain about some artefacts in the Scala runtime, the way it is compiled by the `scalac` compiler (at least in Scala 2.9.1 and older). Note that this option should always be used with care. The additional [`-keep`](usage.md#keepoverview) options make sure that some classes and some fields that are accessed by means of introspection are not removed or renamed. If applicable, you should add options for processing [native methods](#native), [callback methods](#callback), [enumerations](#enumerations), [serializable classes](#serializable), [bean classes](#beans), [annotations](#annotations), and [resource files](#resourcefiles). ## Processing common code constructs {: #commonconstructs} ### Processing native methods {: #native} If your application, applet, servlet, library, etc., contains native methods, you'll want to preserve their names and their classes' names, so they can still be linked to the native library. The following additional option will ensure that: ```proguard -keepclasseswithmembernames,includedescriptorclasses class * { native ; } ``` Note the use of [`-keepclasseswithmembernames`](usage.md#keepclasseswithmembernames). We don't want to preserve all classes or all native methods; we just want to keep the relevant names from being obfuscated. The modifier [includedescriptorclasses](usage.md#includedescriptorclasses) additionally makes sure that the return types and parameter types aren't renamed either, so the entire signatures remain compatible with the native libraries. ProGuard doesn't look at your native code, so it won't automatically preserve the classes or class members that are invoked by the native code. These are entry points, which you'll have to specify explicitly. [Callback methods](#callback) are discussed below as a typical example. ### Processing callback methods {: #callback} If your application, applet, servlet, library, etc., contains callback methods, which are called from external code (native code, scripts,...), you'll want to preserve them, and probably their classes too. They are just entry points to your code, much like, say, the main method of an application. If they aren't preserved by other [`-keep`](usage.md#keep) options, something like the following option will keep the callback class and method: ```proguard -keep class com.example.MyCallbackClass { void myCallbackMethod(java.lang.String); } ``` This will preserve the given class and method from being removed or renamed. ### Processing enumeration classes {: #enumerations} If your application, applet, servlet, library, etc., contains enumeration classes, you'll have to preserve some special methods. Enumerations were introduced in Java 5. The java compiler translates enumerations into classes with a special structure. Notably, the classes contain implementations of some static methods that the run-time environment accesses by introspection (Isn't that just grand? Introspection is the self-modifying code of a new generation). You have to specify these explicitly, to make sure they aren't removed or obfuscated: ```proguard -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } ``` ### Processing serializable classes {: #serializable} More complex applications, applets, servlets, libraries, etc., may contain classes that are serialized. Depending on the way in which they are used, they may require special attention: - Often, serialization is simply a means of transporting data, without long-term storage. Classes that are shrunk and obfuscated should then continue to function fine with the following additional options: ```proguard -keepclassmembers class * implements java.io.Serializable { private static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } ``` The [`-keepclassmembers`](usage.md#keepclassmembers) option makes sure that any serialization methods are kept. By using this option instead of the basic `-keep` option, we're not forcing preservation of *all* serializable classes, just preservation of the listed members of classes that are actually used. - Sometimes, the serialized data are stored, and read back later into newer versions of the serializable classes. One then has to take care the classes remain compatible with their unprocessed versions and with future processed versions. In such cases, the relevant classes will most likely have `serialVersionUID` fields. The following options should then be sufficient to ensure compatibility over time: ```proguard -keepnames class * implements java.io.Serializable -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient ; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } ``` The `serialVersionUID` and `serialPersistentFields` lines makes sure those fields are preserved, if they are present. The `` line preserves all non-static, non-transient fields, with their original names. The introspection of the serialization process and the de-serialization process will then find consistent names. - Occasionally, the serialized data have to remain compatible, but the classes involved lack `serialVersionUID` fields. I imagine the original code will then be hard to maintain, since the serial version UID is then computed from a list of features the serializable class. Changing the class ever so slightly may change the computed serial version UID. The list of features is specified in the section on [Stream Unique Identifiers](http://docs.oracle.com/javase/8/docs/platform/serialization/spec/class.html#a4100) of Sun's [Java Object Serialization Specification](http://docs.oracle.com/javase/8/docs/platform/serialization/spec/serialTOC.html). The following directives should at least partially ensure compatibility with the original classes: ```proguard -keepnames class * implements java.io.Serializable -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient ; !private ; !private ; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } ``` The new options force preservation of the elements involved in the UID computation. In addition, the user will have to manually specify all interfaces of the serializable classes (using something like "`-keep interface MyInterface`"), since these names are also used when computing the UID. A fast but sub-optimal alternative would be simply keeping all interfaces with "`-keep interface *`". - In the rare event that you are serializing lambda expressions in Java 8 or higher, you need to preserve some methods and adapt the hard-coded names of the classes in which they occur: ```proguard -keepclassmembers class * { private static synthetic java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda); } -keepclassmembernames class * { private static synthetic *** lambda$*(...); } -adaptclassstrings com.example.Test ``` This should satisfy the reflection in the deserialization code of the Java run-time. Note that the above options may preserve more classes and class members than strictly necessary. For instance, a large number of classes may implement the `Serialization` interface, yet only a small number may actually ever be serialized. Knowing your application and tuning the configuration often produces more compact results. ### Processing bean classes {: #beans} If your application, applet, servlet, library, etc., makes extensive use of introspection on bean classes to find bean editor classes, or getter and setter methods, then configuration may become painful. There's not much else you can do than making sure the bean class names, or the getter and setter names don't change. For instance: ```proguard -keep public class com.example.MyBean { public void setMyProperty(int); public int getMyProperty(); } -keep public class com.example.MyBeanEditor ``` If there are too many elements to list explicitly, wildcards in class names and method signatures might be helpful. This example preserves all possible setters and getters in classes in the package `mybeans`: ```proguard -keep class mybeans.** { void set*(***); void set*(int, ***); boolean is*(); boolean is*(int); *** get*(); *** get*(int); } ``` The '`***`' wildcard matches any type (primitive or non-primitive, array or non-array). The methods with the '`int`' arguments matches properties that are lists. ### Processing annotations {: #annotations} If your application, applet, servlet, library, etc., uses annotations, you may want to preserve them in the processed output. Annotations are represented by attributes that have no direct effect on the execution of the code. However, their values can be retrieved through introspection, allowing developers to adapt the execution behavior accordingly. By default, ProGuard treats annotation attributes as optional, and removes them in the obfuscation step. If they are required, you'll have to specify this explicitly: ```proguard -keepattributes *Annotation* ``` For brevity, we're specifying a wildcarded attribute name, which will match `RuntimeVisibleAnnotations`, `RuntimeInvisibleAnnotations`, `RuntimeVisibleParameterAnnotations`, `RuntimeInvisibleParameterAnnotations`, and `AnnotationDefault`. Depending on the purpose of the processed code, you could refine this selection, for instance not keeping the run-time invisible annotations (which are only used at compile-time). Some code may make further use of introspection to figure out the enclosing methods of anonymous inner classes. In that case, the corresponding attribute has to be preserved as well: ```proguard -keepattributes EnclosingMethod ``` ### Processing database drivers {: #database} Database drivers are implementations of the `Driver` interface. Since they are often created dynamically, you may want to preserve any implementations that you are processing as entry points: ```proguard -keep class * implements java.sql.Driver ``` This option also gets rid of the note that ProGuard prints out about `(java.sql.Driver)Class.forName` constructs, if you are instantiating a driver in your code (without necessarily implementing any drivers yourself). ### Processing ComponentUI classes {: #componentui} Swing UI look and feels are implemented as extensions of the `ComponentUI` class. For some reason, these have to contain a static method `createUI`, which the Swing API invokes using introspection. You should therefore always preserve the method as an entry point, for instance like this: ```proguard -keep class * extends javax.swing.plaf.ComponentUI { public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent); } ``` This option also keeps the classes themselves. ## Processing common libraries {: #commonlibraries} ### Processing RMI code {: #rmi} Reportedly, the easiest way to handle RMI code is to process the code with ProGuard first and then invoke the `rmic` tool. If that is not possible, you may want to try something like this: ```proguard -keepattributes Exceptions -keep interface * extends java.rmi.Remote { ; } -keep class * implements java.rmi.Remote { (java.rmi.activation.ActivationID, java.rmi.MarshalledObject); {: #activation} } ``` The first [`-keep`](usage.md#keep) option keeps all your Remote interfaces and their methods. The second one keeps all the implementations, along with their particular RMI constructors, if any. The `Exceptions` attribute has to be kept too, because the RMI handling code performs introspection to check whether the method signatures are compatible. ### Optimizing Gson code {: #gson} ProGuard [optimizes Gson code](optimizations.md#gson), by detecting which domain classes are serialized using the Gson library, and then replacing the reflection-based implementation by more efficient hard-coded serialization. The GSON optimization is enabled by default and doesn't require any additional configuration. If you've disabled optimization, the GSON library still relies on reflection on the fields of the classes that it serializes. You then need to preserve the parameterless constructor and the serialized fields from being removed, optimized, or obfuscated. For example: ```proguard -keepclassmembers class com.example.SerializedClass { ; (); } ``` While creating the configuration, you can specify the option [`-addconfigurationdebugging`](usage.md#addconfigurationdebugging), to get feedback on the necessary settings at run-time. Alternatively, you can make sure the fields are explicitly annotated with `@SerializedName`, so the names of the fields can be obfuscated. You can then keep all of them at the same time with: ```proguard -keepclasseswithmembers,allowobfuscation,includedescriptorclasses class * { @com.google.gson.annotations.SerializedName ; } -keepclassmembers enum * { @com.google.gson.annotations.SerializedName ; } ``` ### Processing dependency injection {: #injection} If your application is using JEE-style dependency injection, the application container will automatically assign instances of resource classes to fields and methods that are annotated with `@Resource`. The container applies introspection, even accessing private class members directly. It typically constructs a resource name based on the type name and the class member name. We then have to avoid that such class members are removed or renamed: ```proguard -keepclassmembers class * { @javax.annotation.Resource *; } ``` The Spring framework has another similar annotation `@Autowired`: ```proguard -keepclassmembers class * { @org.springframework.beans.factory.annotation.Autowired *; } ``` ## Further processing possibilities {: #furtherpossibilities} ### Processing resource files {: #resourcefiles} If your application, applet, servlet, library, etc., contains resource files, it may be necessary to adapt their names and/or their contents when the application is obfuscated. The following two options can achieve this automatically: ```proguard -adaptresourcefilenames **.properties,**.gif,**.jpg -adaptresourcefilecontents **.properties,META-INF/MANIFEST.MF ``` The [`-adaptresourcefilenames`](usage.md#adaptresourcefilenames) option in this case renames properties files and image files in the processed output, based on the obfuscated names of their corresponding class files (if any). The [`-adaptresourcefilecontents`](usage.md#adaptresourcefilecontents) option looks for class names in properties files and in the manifest file, and replaces these names by the obfuscated names (if any). You'll probably want to adapt the filters to suit your application. ### Processing manifest files {: #manifestfiles} As illustrated in the previous section, manifest files can be treated like ordinary resource files. ProGuard can adapt obfuscated class names in the files, but it won't make any other changes. If you want anything else, you should apply an external tool. For instance, if a manifest file contains signing information, you should sign the jar again after it has been processed. If you're merging several input jars into a single output jar, you'll have to pick one, typically by specifying [filters](usage.md#filters): ```proguard -injars in1.jar -injars in2.jar(!META-INF/MANIFEST.MF) -injars in3.jar(!META-INF/MANIFEST.MF) -outjars out.jar ``` The filters will let ProGuard copy the manifest file from the first jar and ignore any manifest files in the second and third input jars. Note that ProGuard will leave the order of the files in the jars unchanged; manifest files are not necessarily put first. ### Producing useful obfuscated stack traces {: #stacktrace} These options let obfuscated applications or libraries produce stack traces that can still be deciphered later on: ```proguard -printmapping out.map -renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable ``` We're keeping all source file attributes, but we're replacing their values by the string "SourceFile". We could use any string. This string is already present in all class files, so it doesn't take up any extra space. If you're working with J++, you'll want to keep the "SourceDir" attribute as well. We're also keeping the line number tables of all methods. Whenever both of these attributes are present, the Java run-time environment will include line number information when printing out exception stack traces. The information will only be useful if we can map the obfuscated names back to their original names, so we're saving the mapping to a file `out.map`. The information can then be used by the [ReTrace](../tools/retrace.md) tool to restore the original stack trace. ### Obfuscating package names {: #repackaging} Package names can be obfuscated in various ways, with increasing levels of obfuscation and compactness. For example, consider the following classes: ```proguard mycompany.myapplication.MyMain mycompany.myapplication.Foo mycompany.myapplication.Bar mycompany.myapplication.extra.FirstExtra mycompany.myapplication.extra.SecondExtra mycompany.util.FirstUtil mycompany.util.SecondUtil ``` Let's assume the class name `mycompany.myapplication.MyMain` is the main application class that is kept by the configuration. All other class names can be obfuscated. By default, packages that contain classes that can't be renamed aren't renamed either, and the package hierarchy is preserved. This results in obfuscated class names like these: ```proguard mycompany.myapplication.MyMain mycompany.myapplication.a mycompany.myapplication.b mycompany.myapplication.a.a mycompany.myapplication.a.b mycompany.a.a mycompany.a.b ``` The [`-flattenpackagehierarchy`](usage.md#flattenpackagehierarchy) option obfuscates the package names further, by flattening the package hierarchy of obfuscated packages: ```proguard -flattenpackagehierarchy 'myobfuscated' ``` The obfuscated class names then look as follows: ```proguard mycompany.myapplication.MyMain mycompany.myapplication.a mycompany.myapplication.b myobfuscated.a.a myobfuscated.a.b myobfuscated.b.a myobfuscated.b.b ``` Alternatively, the [`-repackageclasses`](usage.md#repackageclasses) option obfuscates the entire packaging, by combining obfuscated classes into a single package: ```proguard -repackageclasses 'myobfuscated' ``` The obfuscated class names then look as follows: ```proguard mycompany.myapplication.MyMain mycompany.myapplication.a mycompany.myapplication.b myobfuscated.a myobfuscated.b myobfuscated.c myobfuscated.d ``` Additionally specifying the [`-allowaccessmodification`](usage.md#allowaccessmodification) option allows access permissions of classes and class members to be broadened, opening up the opportunity to repackage all obfuscated classes: ```proguard -repackageclasses 'myobfuscated' -allowaccessmodification ``` The obfuscated class names then look as follows: ```proguard mycompany.myapplication.MyMain myobfuscated.a myobfuscated.b myobfuscated.c myobfuscated.d myobfuscated.e myobfuscated.f ``` The specified target package can always be the root package. For instance: ```proguard -repackageclasses '' -allowaccessmodification ``` The obfuscated class names are then the shortest possible names: ```proguard mycompany.myapplication.MyMain a b c d e f ``` Note that not all levels of obfuscation of package names may be acceptable for all code. Notably, you may have to take into account that your application may contain [resource files](#resourcefiles) that have to be adapted. ### Removing logging code {: #logging} You can let ProGuard remove logging code. The trick is to specify that the logging methods don't have side-effects — even though they actually do, since they write to the console or to a log file. ProGuard will take your word for it and remove the invocations (in the optimization step) and if possible the logging classes and methods themselves (in the shrinking step). For example, this configuration removes invocations of some logging methods: ```proguard -assumenosideeffects class com.example.MyLogger { public static boolean isLoggable(java.lang.String, int); public static int v(...); public static int i(...); public static int w(...); public static int d(...); public static int e(...); } ``` The wildcards are a shortcut to match all versions of the methods. Be careful not to use a `*` wildcard to match all methods, because it would also match methods like `wait()`, higher up the hierarchy. Removing those invocations will generally break your code. Note that you generally can't remove logging code that uses `System.out.println`, since you would be removing all invocations of `java.io.PrintStream#println`, which could break your application. You can work around it by creating your own logging methods and let ProGuard remove those. Logging statements often contain implicit calls that perform string concatenation. They no longer serve a purpose after the logging calls have been removed. You can let ProGuard clean up such constructs as well by providing additional hints: ```proguard -assumenoexternalsideeffects class java.lang.StringBuilder { public java.lang.StringBuilder(); public java.lang.StringBuilder(int); public java.lang.StringBuilder(java.lang.String); public java.lang.StringBuilder append(java.lang.Object); public java.lang.StringBuilder append(java.lang.String); public java.lang.StringBuilder append(java.lang.StringBuffer); public java.lang.StringBuilder append(char[]); public java.lang.StringBuilder append(char[], int, int); public java.lang.StringBuilder append(boolean); public java.lang.StringBuilder append(char); public java.lang.StringBuilder append(int); public java.lang.StringBuilder append(long); public java.lang.StringBuilder append(float); public java.lang.StringBuilder append(double); public java.lang.String toString(); } -assumenoexternalreturnvalues public final class java.lang.StringBuilder { public java.lang.StringBuilder append(java.lang.Object); public java.lang.StringBuilder append(java.lang.String); public java.lang.StringBuilder append(java.lang.StringBuffer); public java.lang.StringBuilder append(char[]); public java.lang.StringBuilder append(char[], int, int); public java.lang.StringBuilder append(boolean); public java.lang.StringBuilder append(char); public java.lang.StringBuilder append(int); public java.lang.StringBuilder append(long); public java.lang.StringBuilder append(float); public java.lang.StringBuilder append(double); } ``` Be careful specifying your own assumptions, since they can easily break your code. ### Restructuring the output archives {: #restructuring} In simple applications, all output classes and resources files are merged into a single jar. For example: ```proguard -injars classes -injars in1.jar -injars in2.jar -injars in3.jar -outjars out.jar ``` This configuration merges the processed versions of the files in the `classes` directory and the three jars into a single output jar `out.jar`. If you want to preserve the structure of your input jars (and/or apks, aars, wars, ears, jmods, zips, or directories), you can specify an output directory (or an apk, an aar, a war, an ear, a jmod, or a zip). For example: ```proguard -injars in1.jar -injars in2.jar -injars in3.jar -outjars out ``` The input jars will then be reconstructed in the directory `out`, with their original names. You can also combine archives into higher level archives. For example: ```proguard -injars in1.jar -injars in2.jar -injars in3.jar -outjars out.war ``` The other way around, you can flatten the archives inside higher level archives into simple archives: ```proguard -injars in.war -outjars out.jar ``` This configuration puts the processed contents of all jars inside `in.war` (plus any other contents of `in.war`) into `out.jar`. If you want to combine input jars (and/or apks, aars, wars, ears, jmods, zips, or directories) into output jars (and/or apks, aars, wars, ears, jmods, zips, or directories), you can group the [`-injars`](usage.md#injars) and [`-outjars`](usage.md#outjars) options. For example: ```proguard -injars base_in1.jar -injars base_in2.jar -injars base_in3.jar -outjars base_out.jar -injars extra_in.jar -outjars extra_out.jar ``` This configuration puts the processed results of all `base_in*.jar` jars into `base_out.jar`, and the processed results of the `extra_in.jar` into `extra_out.jar`. Note that only the order of the options matters; the additional whitespace is just for clarity. This grouping, archiving, and flattening can be arbitrarily complex. ProGuard always tries to package output archives in a sensible way, reconstructing the input entries as much as required. ### Filtering the input and the output {: #filtering} If you want even greater control, you can add [filters](usage.md#filters) to the input and the output, filtering out apks, jars, aars, wars, ears, jmods, zips, and/or ordinary files. For example, if you want to disregard certain files from an input jar: ```proguard -injars in.jar(!images/**) -outjars out.jar ``` This configuration removes any files in the `images` directory and its subdirectories. Such filters can be convenient for avoiding warnings about duplicate files in the output. For example, only keeping the manifest file from a first input jar: ```proguard -injars in1.jar -injars in2.jar(!META-INF/MANIFEST.MF) -injars in3.jar(!META-INF/MANIFEST.MF) -outjars out.jar ``` Another useful application is ignoring unwanted files from the runtime library module: ```proguard -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) ``` The filter makes ProGuard disregard redundant jars inside the module, and module info classes that would only cause conflicts with duplicate names. It is also possible to filter the jars (and/or apks, aabs, aars, wars, ears, jmods, zips) themselves, based on their names. For example: ```proguard -injars in(**/acme_*.jar;) -outjars out.jar ``` Note the semi-colon in the filter; the filter in front of it applies to jar names. In this case, only `acme_*.jar` jars are read from the directory `in` and its subdirectories. Filters for war names, ear names, and zip names can be prefixed with additional semi-colons. All types of filters can be combined. They are orthogonal. On the other hand, you can also filter the output, in order to control what content goes where. For example: ```proguard -injars in.jar -outjars code_out.jar(**.class) -outjars resources_out.jar ``` This configuration splits the processed output, sending `**.class` files to `code_out.jar`, and all remaining files to `resources_out.jar`. Again, the filtering can be arbitrarily complex, especially when combined with grouping input and output. ### Processing multiple applications at once {: #multiple} You can process several dependent or independent applications (or applets, midlets,...) in one go, in order to save time and effort. ProGuard's input and output handling offers various ways to keep the output nicely structured. The easiest way is to specify your input jars (and/or wars, ears, zips, and directories) and a single output directory. ProGuard will then reconstruct the input in this directory, using the original jar names. For example, showing just the input and output options: ```proguard -injars application1.jar -injars application2.jar -injars application3.jar -outjars processed_applications ``` After processing, the directory `processed_applications` will contain processed versions of application jars, with their original names. ### Incremental obfuscation {: #incremental} After having [processed an application](#application), e.g. ProGuard itself, you can still incrementally add other pieces of code that depend on it, e.g. the ProGuard GUI: ```proguard -injars proguardgui.jar -outjars proguardgui_out.jar -injars proguard.jar -outjars proguard_out.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -applymapping proguard.map -keep public class proguard.gui.ProGuardGUI { public static void main(java.lang.String[]); } ``` We're reading both unprocessed jars as input. Their processed contents will go to the respective output jars. The [`-applymapping`](usage.md#applymapping) option then makes sure the ProGuard part of the code gets the previously produced obfuscation mapping. The final application will consist of the obfuscated ProGuard jar and the additional obfuscated GUI jar. The added code in this example is straightforward; it doesn't affect the original code. The `proguard_out.jar` will be identical to the one produced in the initial processing step. If you foresee adding more complex extensions to your code, you should specify the options [`-useuniqueclassmembernames`](usage.md#useuniqueclassmembernames), [`-dontshrink`](usage.md#dontshrink), and [`-dontoptimize`](usage.md#dontoptimize) *in the original processing step*. These options ensure that the obfuscated base jar will always remain usable without changes. You can then specify the base jar as a library jar: ```proguard -injars proguardgui.jar -outjars proguardgui_out.jar -libraryjars proguard.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -applymapping proguard.map -keep public class proguard.gui.ProGuardGUI { public static void main(java.lang.String[]); } ``` ## Other uses {: #otheruses} ### Preverifying class files for Java Micro Edition {: #microedition} Even if you're not interested in shrinking, optimizing, and obfuscating your midlets, as shown in the [midlets example](#midlets), you can still use ProGuard to preverify the class files for Java Micro Edition. ProGuard produces slightly more compact results than the traditional external preverifier. ```proguard -injars in.jar -outjars out.jar -libraryjars /usr/local/java/wtk2.5.2/lib/midpapi20.jar -libraryjars /usr/local/java/wtk2.5.2/lib/cldcapi11.jar -dontshrink -dontoptimize -dontobfuscate -microedition ``` We're not processing the input, just making sure the class files are preverified by targeting them at Java Micro Edition with the [`-microedition`](usage.md#microedition) option. Note that we don't need any [`-keep`](usage.md#keep) options to specify entry points; all class files are simply preverified. ### Upgrading old class files to Java 6 {: #upgrade} The following options upgrade class files to Java 6, by updating their internal version numbers and preverifying them. The class files can then be loaded more efficiently by the Java 6 Virtual Machine. ```proguard -injars in.jar -outjars out.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -dontshrink -dontoptimize -dontobfuscate -target 1.6 ``` We're not processing the input, just retargeting the class files with the [`-target`](usage.md#target) option. They will automatically be preverified for Java 6 as a result. Note that we don't need any `-keep` options to specify entry points; all class files are simply updated and preverified. ### Finding dead code {: #deadcode} These options list unused classes, fields, and methods in the application `com.example.MyApplication`: ```proguard -injars in.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -dontoptimize -dontobfuscate -dontpreverify -printusage -keep public class com.example.MyApplication { public static void main(java.lang.String[]); } ``` We're not specifying an output jar, just printing out some results. We're saving some processing time by skipping the other processing steps. The java compiler inlines primitive constants and String constants (`static final` fields). ProGuard would therefore list such fields as not being used in the class files that it analyzes, even if they *are* used in the source files. We can add a [`-keepclassmembers`](usage.md#keepclassmembers) option that keeps those fields a priori, in order to avoid having them listed: ```proguard -keepclassmembers class * { static final % *; static final java.lang.String *; } ``` ### Printing out the internal structure of class files {: #structure} These options print out the internal structure of all class files in the input jar: ```proguard -injars in.jar -dontshrink -dontoptimize -dontobfuscate -dontpreverify -dump ``` Note how we don't need to specify the Java run-time jar, because we're not processing the input jar at all. ### Using annotations to configure ProGuard {: #annotated} The traditional ProGuard configuration allows to keep a clean separation between the code and the configuration for shrinking, optimization, and obfuscation. However, it is also possible to define specific annotations, and then annotate the code to configure the processing. You can find a set of such predefined annotations in `lib/annotations.jar` in the ProGuard distribution. The corresponding ProGuard configuration (or meta-configuration, if you prefer) is specified in `annotations/annotations.pro`. With these files, you can start annotating your code. For instance, a java source file `Application.java` can be annotated as follows: ```java @KeepApplication public class Application { .... } ``` The ProGuard configuration file for the application can then be simplified by leveraging these annotations: ```proguard -injars in.jar -outjars out.jar -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -include lib/annotations.pro ``` The annotations are effectively replacing the application-dependent `-keep` options. You may still wish to add traditional [`-keep`](usage.md#keep) options for processing [native methods](#native), [enumerations](#enumerations), [serializable classes](#serializable), and [annotations](#annotations). The directory `examples/annotations` contains more examples that illustrate some of the possibilities. ================================================ FILE: docs/md/manual/configuration/optimizations.md ================================================ The optimization step of ProGuard can be switched off with the [`-dontoptimize`](usage.md#dontoptimize) option. For more fine-grained control over individual optimizations, experts can use the [`-optimizations`](usage.md#optimizations) option, with a filter based on the optimization names listed below. The filter works like any [filter](usage.md#filters) in ProGuard. The following wildcards are supported: | Wildcard | Meaning |-----|------------------------------------------------------- | `?` | matches any single character in an optimization name. | `*` | matches any part of an optimization name. An optimization that is preceded by an exclamation mark '**!**' is *excluded* from further attempts to match with *subsequent* optimization names in the filter. Make sure to specify filters correctly, since they are not checked for potential typos. For example, "**`code/simplification/variable,code/simplification/arithmetic`**" only performs the two specified peephole optimizations. For example, "`!method/propagation/*`" performs all optimizations, except the ones that propagate values between methods. For example, "`!code/simplification/advanced,code/simplification/*`" only performs all peephole optimizations. Some optimizations necessarily imply other optimizations. These are then indicated. Note that the list is likely to change for newer versions, as optimizations are added and reorganized. `library/gson` : Optimizes usages of the Gson library, whenever possible. See [Gson optimization](optimizations.md#gson) for more details. `class/marking/final` : Marks classes as final, whenever possible. `class/unboxing/enum` : Simplifies enum types to integer constants, whenever possible. `class/merging/vertical` : Merges classes vertically in the class hierarchy, whenever possible. `class/merging/horizontal` : Merges classes horizontally in the class hierarchy, whenever possible. `class/merging/wrapper` : Merges wrapper classes with their wrapped classes, whenever possible. `field/removal/writeonly`
(⇒ `code/removal/advanced`)
: Removes write-only fields. `field/marking/private` : Marks fields as private, whenever possible. `field/generalization/class` : Generalizes the classes of field accesses, whenever possible. `field/specialization/type` : Specializes the types of fields, whenever possible `field/propagation/value`
(⇒ `code/simplification/advanced`)
: Propagates the values of fields across methods. `method/marking/private` : Marks methods as private, whenever possible (*devirtualization*). `method/marking/static`
(⇒ `code/removal/advanced`)
: Marks methods as static, whenever possible (*devirtualization*). `method/marking/final` : Marks methods as final, whenever possible. `method/marking/synchronized` : Unmarks methods as synchronized, whenever possible. `method/removal/parameter`
(⇒ `code/removal/advanced`)
: Removes unused method parameters. `method/generalization/class` : Generalizes the classes of method invocations, whenever possible. `method/specialization/parametertype` : Specializes the types of method parameters, whenever possible. `method/specialization/returntype` : Specializes the types of method return values, whenever possible. `method/propagation/parameter`
(⇒ `code/simplification/advanced`)
: Propagates the values of method parameters from method invocations to the invoked methods. `method/propagation/returnvalue`
(⇒ `code/simplification/advanced`)
: Propagates the values of method return values from methods to their invocations. `method/inlining/short` : Inlines short methods. `method/inlining/unique` : Inlines methods that are only called once. `method/inlining/tailrecursion` : Simplifies tail recursion calls, whenever possible. `code/merging` : Merges identical blocks of code by modifying branch targets. `code/simplification/variable` : Performs peephole optimizations for variable loading and storing. `code/simplification/arithmetic` : Performs peephole optimizations for arithmetic instructions. `code/simplification/cast` : Performs peephole optimizations for casting operations. `code/simplification/field` : Performs peephole optimizations for field loading and storing. `code/simplification/branch`
(⇒ `code/removal/simple`)
: Performs peephole optimizations for branch instructions. `code/simplification/object` : Performs peephole optimizations for object instantiation. `code/simplification/string` : Performs peephole optimizations for constant strings. `code/simplification/math` : Performs peephole optimizations for Math method calls. `code/simplification/advanced`
(*best used with* `code/removal/advanced`)
: Simplifies code based on control flow analysis and data flow analysis. `code/removal/advanced`
(⇒ `code/removal/exception`)
: Removes dead code based on control flow analysis and data flow analysis. `code/removal/simple`
(⇒ `code/removal/exception`)
: Removes dead code based on a simple control flow analysis. `code/removal/variable` : Removes unused variables from the local variable frame. `code/removal/exception` : Removes exceptions with empty try blocks. `code/allocation/variable` : Optimizes variable allocation on the local variable frame. ProGuard also provides some unofficial settings to control optimizations, that may disappear in future versions. These are Java system properties, which can be set as JVM arguments (with `-D...`): `maximum.inlined.code.length` (default = 8 bytes) : Specifies the maximum code length (expressed in bytes) of short methods that are eligible to be inlined. Inlining methods that are too long may unnecessarily inflate the code size. `maximum.resulting.code.length` (default = 8000 bytes for JSE, 2000 bytes for JME) : Specifies the maximum resulting code length (expressed in bytes) allowed when inlining methods. Many Java virtual machines do not apply just-in-time compilation to methods that are too long, so it's important not to let them grow too large. ## Aggressive optimization ProGuard provides the `-optimizeaggressively` option. If set, this enables more aggressive assumptions during optimization. This might lead to improved performance and/or reduced code size, but might result in different behavior in rare cases. For example, reading from an array might cause an `ArrayIndexOutOfBoundsException` to be thrown. Strictly speaking, this means that such an instruction can have a side effect. If this instruction is removed during optimization, the code will thus behave differently under specific circumstances. By default, such instructions are always preserved. Setting this option will lead to these instructions being candidates for removal during optimization. Additionally, class merging is only enabled when this option is set. ## Gson optimization {: #gson} ProGuard optimizes Gson code by detecting which domain classes are serialized using the Gson library. It replaces the reflection-based implementation of GSON for reading and writing fields with injected and optimized code that accesses the fields of the domain classes directly when reading and writing JSON. The benefits of this optimization are the following: - Domain classes used in conjunction with GSON can be freely obfuscated. - The injected serialization code gives better performance compared to the GSON implementation, which relies on reflection. - Less configuration is needed as the optimization automatically keeps classes and fields that are required for serialization. ### Configuration The Gson optimization is enabled by default and doesn't require any additional configuration, as long as the application code doesn't use unsupported Gson features(see [Known limitations](optimizations.md#gsonlimitations)). ### Known limitations {: #gsonlimitations} ProGuard can not optimize the following use cases of Gson: - Serializing classes containing one of the following Gson annotations: - `@JsonAdapter` - `@Since` - `@Until` - Serializing classes that have generic type variables in their signature. - Serializing classes using a Gson instance that was built with one of the following settings on the GsonBuilder: - `excludeFieldsWithModifier` - `setFieldNamingPolicy` When one of the above Gson features is used, ProGuard automatically preserves the original Gson implementation for all affected domain classes. This means that the serialized fields of these domain classes need to be explicitly kept again in the ProGuard configuration so that they can be safely accessed through reflection. ================================================ FILE: docs/md/manual/configuration/usage.md ================================================ This page lists all available options for ProGuard, grouped logically. !!! android R8 R8, the default Android shrinker, is compatible with ProGuard keep rules. ## Input/Output Options {: #iooptions} `@`{: #at} [*filename*](#filename) : Short for '[`-include`](#include) [*filename*](#filename)'. `-include`{: #include} [*filename*](#filename) : Recursively reads configuration options from the given file *filename*. `-basedirectory`{: #basedirectory} [*directoryname*](#filename) : Specifies the base directory for all subsequent relative file names in these configuration arguments or this configuration file. `-injars`{: #injars} [*class\_path*](#classpath) : Specifies the input jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories) of the application to be processed. The class files in these jars will be processed and written to the output jars. By default, any non-class files will be copied without changes. Please be aware of any temporary files (e.g. created by IDEs), especially if you are reading your input files straight from directories. The entries in the class path can be filtered, as explained in the [filters](#filefilters) section. For better readability, class path entries can be specified using multiple `-injars` options. `-outjars`{: #outjars} [*class\_path*](#classpath) : Specifies the names of the output jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). The processed input of the preceding [`-injars`](usage.md#injars) options will be written to the named jars. This allows you to collect the contents of groups of input jars into corresponding groups of output jars. In addition, the output entries can be filtered, as explained in the [filters](#filefilters) section. Each processed class file or resource file is then written to the first output entry with a matching filter, within the group of output jars. You must avoid letting the output files overwrite any input files. For better readability, class path entries can be specified using multiple [`-outjars`](usage.md#outjars) options. Without any [`-outjars`](usage.md#outjars) options, no jars will be written. `-libraryjars`{: #libraryjars} [*class\_path*](#classpath) : Specifies the library jars (or apks, aabs, aars, wars, ears, jmods, zips, directories) of the application to be processed. The files in these jars will not be included in the output jars. The specified library jars should at least contain the class files that are *extended* by application class files. Library class files that are only *called* needn't be present, although their presence can improve the results of the optimization step. The entries in the class path can be filtered, as explained in the [filters](#filefilters) section. For better readability, class path entries can be specified using multiple `-libraryjars` options. Please note that the boot path and the class path set for running ProGuard are not considered when looking for library classes. This means that you explicitly have to specify the run-time jar that your code will use. Although this may seem cumbersome, it allows you to process applications targeted at different run-time environments. For example, you can process [J2SE applications](examples.md#application) as well as [JME midlets](examples.md#midlet), just by specifying the appropriate run-time jar. `-skipnonpubliclibraryclasses`{: #skipnonpubliclibraryclasses} : Specifies to skip non-public classes while reading library jars, to speed up processing and reduce memory usage of ProGuard. By default, ProGuard reads non-public and public library classes alike. However, non-public classes are often not relevant, if they don't affect the actual program code in the input jars. Ignoring them then speeds up ProGuard, without affecting the output. Unfortunately, some libraries, including recent JSE run-time libraries, contain non-public library classes that are extended by public library classes. You then can't use this option. ProGuard will print out warnings if it can't find classes due to this option being set. `-dontskipnonpubliclibraryclasses`{: #dontskipnonpubliclibraryclasses} : Specifies not to ignore non-public library classes. As of version 4.5, this is the default setting. `-dontskipnonpubliclibraryclassmembers`{: #dontskipnonpubliclibraryclassmembers} : Specifies not to ignore package visible library class members (fields and methods). By default, ProGuard skips these class members while parsing library classes, as program classes will generally not refer to them. Sometimes however, program classes reside in the same packages as library classes, and they do refer to their package visible class members. In those cases, it can be useful to actually read the class members, in order to make sure the processed code remains consistent. `-keepdirectories`{: #keepdirectories} \[*[directory\_filter](#filefilters)*\] : Specifies the directories to be kept in the output jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). By default, directory entries are removed. This reduces the jar size, but it may break your program if the code tries to find them with constructs like "`com.example.MyClass.class.getResource("")`". You'll then want to keep the directory corresponding to the package, "`-keepdirectories com.example`". If the option is specified without a filter, all directories are kept. With a filter, only matching directories are kept. For instance, "`-keepdirectories mydirectory`" matches the specified directory, "`-keepdirectories mydirectory/*`" matches its immediate subdirectories, and "`-keepdirectories mydirectory/**`" matches all of its subdirectories. `-target`{: #target} *version* : **Deprecated: this option is only applicable for Java class file versions <= 11.** Specifies the version number to be set in the processed class files. The version number can be one of `1.0`,..., `1.9`, or the more recent short numbers `5`,..., `12`. By default, the version numbers of the class files are left unchanged. For example, you may want to [upgrade class files to Java 6](examples.md#upgrade). ProGuard changes their version numbers and preverifies them. You can also downgrade class files to older versions than Java 8. ProGuard changes their version numbers and backports Java 8 constructs. ProGuard generally doesn't backport changes in the Java runtime, except for the Java 8 stream API and the Java 8 date API, if you add the backported libraries `net.sourceforge.streamsupport` and `org.threeten` as input, respectively. `-forceprocessing`{: #forceprocessing} : Specifies to process the input, even if the output seems up to date. The up-to-dateness test is based on a comparison of the date stamps of the specified input, output, and configuration files or directories. ## Keep Options {: #keepoptions} !!! tip "ProGuard Playground" The [**ProGuard Playground**](https://playground.proguard.com) is a useful tool to help you further tweak the keep rules. `-keep`{: #keep} \[[,*modifier*](#keepoptionmodifiers),...\] [*class\_specification*](#classspecification) : Specifies classes and class members (fields and methods) to be preserved as entry points to your code. For example, in order to [keep an application](examples.md#application), you can specify the main class along with its main method. In order to [process a library](examples.md#library), you should specify all publicly accessible elements. `-keepclassmembers`{: #keepclassmembers} \[[,*modifier*](#keepoptionmodifiers),...\] [*class\_specification*](#classspecification) : Specifies class members to be preserved, if their classes are preserved as well. For example, you may want to [keep all serialization fields and methods](examples.md#serializable) of classes that implement the `Serializable` interface. `-keepclasseswithmembers`{: #keepclasseswithmembers} \[[,*modifier*](#keepoptionmodifiers),...\] [*class\_specification*](#classspecification) : Specifies classes and class members to be preserved, on the condition that all of the specified class members are present. For example, you may want to [keep all applications](examples.md#applications) that have a main method, without having to list them explicitly. `-keepnames`{: #keepnames} [*class\_specification*](#classspecification) : Short for [`-keep`](#keep),[`allowshrinking`](#allowshrinking) [*class\_specification*](#classspecification) Specifies classes and class members whose names are to be preserved, if they aren't removed in the shrinking phase. For example, you may want to [keep all class names](examples.md#serializable) of classes that implement the `Serializable` interface, so that the processed code remains compatible with any originally serialized classes. Classes that aren't used at all can still be removed. Only applicable when obfuscating. `-keepclassmembernames`{: #keepclassmembernames} [*class\_specification*](#classspecification) : Short for [`-keepclassmembers`](#keepclassmembers),[`allowshrinking`](#allowshrinking) [*class\_specification*](#classspecification) Specifies class members whose names are to be preserved, if they aren't removed in the shrinking phase. For example, you may want to preserve the name of the synthetic `class$` methods when [processing a library](examples.md#library) compiled by JDK 1.2 or older, so obfuscators can detect it again when processing an application that uses the processed library (although ProGuard itself doesn't need this). Only applicable when obfuscating. `-keepclasseswithmembernames`{: #keepclasseswithmembernames} [*class\_specification*](#classspecification) : Short for [`-keepclasseswithmembers`](#keepclasseswithmembers),[`allowshrinking`](#allowshrinking) [*class\_specification*](#classspecification) Specifies classes and class members whose names are to be preserved, on the condition that all of the specified class members are present after the shrinking phase. For example, you may want to [keep all native method names](examples.md#native) and the names of their classes, so that the processed code can still link with the native library code. Native methods that aren't used at all can still be removed. If a class file is used, but none of its native methods are, its name will still be obfuscated. Only applicable when obfuscating. `-if`{: #if} [*class\_specification*](#classspecification) : Specifies classes and class members that must be `present` to activate the subsequent keep option ([`-keep`](usage.md#keep), [`-keepclassmembers`](usage.md#keepclassmembers),...). The condition and the subsequent keep option can share wildcards and references to wildcards. For example, you can keep classes on the condition that classes with related names exist in your project, with frameworks like [Dagger](examples.md#dagger) and [Butterknife](examples.md#butterknife). `-printseeds`{: #printseeds} \[[*filename*](#filename)\] : Specifies to exhaustively list classes and class members matched by the various `-keep` options. The list is printed to the standard output or to the given file. The list can be useful to verify if the intended class members are really found, especially if you're using wildcards. For example, you may want to list all the [applications](examples.md#applications) or all the [applets](examples.md#applets) that you are keeping. ## Shrinking Options {: #shrinkingoptions} `-dontshrink`{: #dontshrink} : Specifies not to shrink the input. By default, ProGuard shrinks the code: it removes all unused classes and class members. It only keeps the ones listed by the various [`-keep`](usage.md#keep) options, and the ones on which they depend, directly or indirectly. It also applies a shrinking step after each optimization step, since some optimizations may open up the possibility to remove more classes and class members. `-printusage`{: #printusage} \[[*filename*](#filename)\] : Specifies to list dead code of the input class files. The list is printed to the standard output or to the given file. For example, you can [list the unused code of an application](examples.md#deadcode). Only applicable when shrinking. `-whyareyoukeeping`{: #whyareyoukeeping} [*class\_specification*](#classspecification) : Specifies to print details on why the given classes and class members are being kept in the shrinking step. This can be useful if you are wondering why some given element is present in the output. In general, there can be many different reasons. This option prints the shortest chain of methods to a specified seed or entry point, for each specified class and class member. *In the current implementation, the shortest chain that is printed out may sometimes contain circular deductions -- these do not reflect the actual shrinking process.* If the [`-verbose`](#verbose) option if specified, the traces include full field and method signatures. Only applicable when shrinking. ## Optimization Options {: #optimizationoptions} `-dontoptimize`{: #dontoptimize} : Specifies not to optimize the input class files. By default, ProGuard optimizes all code. It inlines and merges classes and class members, and it optimizes all methods at a bytecode level. `-optimizations`{: #optimizations} [*optimization\_filter*](optimizations.md) : Specifies the optimizations to be enabled and disabled, at a more fine-grained level. Only applicable when optimizing. *This is an expert option.* `-optimizationpasses`{: #optimizationpasses} *n* : Specifies the number of optimization passes to be performed. By default, a single pass is performed. Multiple passes may result in further improvements. If no improvements are found after an optimization pass, the optimization is ended. Only applicable when optimizing. `-assumenosideeffects`{: #assumenosideeffects} [*class\_specification*](#classspecification) : Specifies methods that don't have any side effects, other than possibly returning a value. For example, the method `System.currentTimeMillis()` returns a value, but it doesn't have any side effects. In the optimization step, ProGuard can then remove calls to such methods, if it can determine that the return values aren't used. ProGuard will analyze your program code to find such methods automatically. It will not analyze library code, for which this option can therefore be useful. For example, you could specify the method `System.currentTimeMillis()`, so that any idle calls to it will be removed. With some care, you can also use the option to [remove logging code](examples.md#logging). Note that ProGuard applies the option to the entire hierarchy of the specified methods. Only applicable when optimizing. In general, making assumptions can be dangerous; you can easily break the processed code. *Only use this option if you know what you're doing!* `-assumenoexternalsideeffects`{: #assumenoexternalsideeffects} [*class\_specification*](#classspecification) : Specifies methods that don't have any side effects, except possibly on the instances on which they are called. This statement is weaker than [`-assumenosideeffects`](#assumenosideeffects), because it allows side effects on the parameters or the heap. For example, the `StringBuffer#append` methods have side effects, but no external side effects. This is useful when [removing logging code](examples.md#logging), to also remove any related string concatenation code. Only applicable when optimizing. Making assumptions can be dangerous; you can easily break the processed code. *Only use this option if you know what you're doing!* `-assumenoescapingparameters`{: #assumenoescapingparameters} [*class\_specification*](#classspecification) : Specifies methods that don't let their reference parameters escape to the heap. Such methods can use, modify, or return the parameters, but not store them in any fields, either directly or indirectly. For example, the method `System.arrayCopy` does not let its reference parameters escape, but method `System.setSecurityManager` does. Only applicable when optimizing. Making assumptions can be dangerous; you can easily break the processed code. *Only use this option if you know what you're doing!* `-assumenoexternalreturnvalues`{: #assumenoexternalreturnvalues} [*class\_specification*](#classspecification) : Specifies methods that don't return reference values that were already on the heap when they are called. For example, the `ProcessBuilder#start` returns a `Process` reference value, but it is a new instance that wasn't on the heap yet. Only applicable when optimizing. Making assumptions can be dangerous; you can easily break the processed code. *Only use this option if you know what you're doing!* `-assumevalues`{: #assumevalues} [*class\_specification*](#classspecification) : Specifies fixed values or ranges of values for primitive fields and methods. Making assumptions can be dangerous; you can easily break the processed code. *Only use this option if you know what you're doing!* `-allowaccessmodification`{: #allowaccessmodification} : Specifies that the access modifiers of classes and class members may be broadened during processing. This can improve the results of the optimization step. For instance, when inlining a public getter, it may be necessary to make the accessed field public too. Although Java's binary compatibility specifications formally do not require this (cfr. [The Java Language Specification, Third Edition](http://docs.oracle.com/javase/specs/jls/se12/html/index.html), [Section 13.4.6](http://docs.oracle.com/javase/specs/jls/se12/html/jls-13.html#jls-13.4.6)), some virtual machines would have problems with the processed code otherwise. Only applicable when optimizing (and when obfuscating with the [`-repackageclasses`](#repackageclasses) option). *Counter-indication:* you probably shouldn't use this option when processing code that is to be used as a library, since classes and class members that weren't designed to be public in the API may become public. `-mergeinterfacesaggressively`{: #mergeinterfacesaggressively} : Specifies that interfaces may be merged, even if their implementing classes don't implement all interface methods. This can reduce the size of the output by reducing the total number of classes. Note that Java's binary compatibility specifications allow such constructs (cfr. [The Java Language Specification, Third Edition](http://docs.oracle.com/javase/specs/jls/se12/html/index.html), [Section 13.5.3](http://docs.oracle.com/javase/specs/jls/se12/html/jls-13.html#jls-13.5.3)), even if they are not allowed in the Java language (cfr. [The Java Language Specification, Third Edition](http://docs.oracle.com/javase/specs/jls/se12/html/index.html), [Section 8.1.4](http://docs.oracle.com/javase/specs/jls/se12/html/jls-8.html#jls-8.1.4)). Only applicable when optimizing. *Counter-indication:* setting this option can reduce the performance of the processed code on some JVMs, since advanced just-in-time compilation tends to favor more interfaces with fewer implementing classes. Worse, some JVMs may not be able to handle the resulting code. Notably: - Sun's JRE 1.3 may throw an `InternalError` when encountering more than 256 *Miranda* methods (interface methods without implementations) in a class. `-optimizeaggressively`{: #optimizeaggressively} : Enables more aggressive assumptions during optimization. This might lead to improved performance and/or reduced code size, but might result in different behavior in rare cases. For example, reading from an array might cause an `ArrayIndexOutOfBoundsException` to be thrown. Strictly speaking, this means that such an instruction can have a side effect. If this instruction is removed during optimization, the code will thus behave differently under specific circumstances. By default, such instructions are always preserved. Setting this option will lead to these instructions being candidates for removal during optimization. Additionally, class merging is only enabled when this option is set. ## Obfuscation Options {: #obfuscationoptions} `-dontobfuscate`{: #dontobfuscate} : Specifies not to obfuscate the input class files. By default, ProGuard obfuscates the code: it assigns new short random names to classes and class members. It removes internal attributes that are only useful for debugging, such as source files names, variable names, and line numbers. `-printmapping`{: #printmapping} \[[*filename*](#filename)\] : Specifies to print the mapping from old names to new names for classes and class members that have been renamed. The mapping is printed to the standard output or to the given file. For example, it is required for subsequent [incremental obfuscation](examples.md#incremental), or if you ever want to make sense again of [obfuscated stack traces](examples.md#stacktrace). Only applicable when obfuscating. `-applymapping`{: #applymapping} [*filename*](#filename) : Specifies to reuse the given name mapping that was printed out in a previous obfuscation run of ProGuard. Classes and class members that are listed in the mapping file receive the names specified along with them. Classes and class members that are not mentioned receive new names. The mapping may refer to input classes as well as library classes. This option can be useful for [incremental obfuscation](examples.md#incremental), i.e. processing add-ons or small patches to an existing piece of code. If the structure of the code changes fundamentally, ProGuard may print out warnings that applying a mapping is causing conflicts. You may be able to reduce this risk by specifying the option [`-useuniqueclassmembernames`](#useuniqueclassmembernames) in both obfuscation runs. Only a single mapping file is allowed. Only applicable when obfuscating. `-obfuscationdictionary`{: #obfuscationdictionary} [*filename*](#filename) : Specifies a text file from which all valid words are used as obfuscated field and method names. By default, short names like 'a', 'b', etc. are used as obfuscated names. With an obfuscation dictionary, you can specify a list of reserved key words, or identifiers with foreign characters, for instance. White space, punctuation characters, duplicate words, and comments after a `#` sign are ignored. Note that an obfuscation dictionary hardly improves the obfuscation. Decent compilers can automatically replace them, and the effect can fairly simply be undone by obfuscating again with simpler names. The most useful application is specifying strings that are typically already present in class files (such as 'Code'), thus reducing the class file sizes just a little bit more. Only applicable when obfuscating. `-classobfuscationdictionary`{: #classobfuscationdictionary} [*filename*](#filename) : Specifies a text file from which all valid words are used as obfuscated class names. The obfuscation dictionary is similar to the one of the option [`-obfuscationdictionary`](#obfuscationdictionary). Only applicable when obfuscating. `-packageobfuscationdictionary`{: #packageobfuscationdictionary} [*filename*](#filename) : Specifies a text file from which all valid words are used as obfuscated package names. The obfuscation dictionary is similar to the one of the option [`-obfuscationdictionary`](#obfuscationdictionary). Only applicable when obfuscating. `-overloadaggressively`{: #overloadaggressively} : Specifies to apply aggressive overloading while obfuscating. Multiple fields and methods can then get the same names, as long as their arguments and return types are different, as required by Java bytecode (not just their arguments, as required by the Java language). This option can make the processed code even smaller (and less comprehensible). Only applicable when obfuscating. *Counter-indication:* the resulting class files fall within the Java bytecode specification (cfr. [The Java Virtual Machine Specification](http://docs.oracle.com/javase/specs/jvms/se12/html/index.html), first paragraphs of [Section 4.5](http://docs.oracle.com/javase/specs/jvms/se12/html/jvms-4.html#jvms-4.5) and [Section 4.6](http://docs.oracle.com/javase/specs/jvms/se12/html/jvms-4.html#jvms-4.6)), even though this kind of overloading is not allowed in the Java language (cfr. [The Java Language Specification, Third Edition](http://docs.oracle.com/javase/specs/jls/se12/html/index.html), [Section 8.3](http://docs.oracle.com/javase/specs/jls/se12/html/jls-8.html#jls-8.3) and [Section 8.4.5](http://docs.oracle.com/javase/specs/jls/se12/html/jls-8.html#jls-8.4.5)). Still, some tools have problems with it. Notably: - Sun's JDK 1.2.2 `javac` compiler produces an exception when compiling with such a library (cfr. [Bug \#4216736](http://bugs.sun.com/view_bug.do?bug_id=4216736)). You probably shouldn't use this option for processing libraries. - Sun's JRE 1.4 and later fail to serialize objects with overloaded primitive fields. - Sun's JRE 1.5 `pack200` tool reportedly has problems with overloaded class members. - The class `java.lang.reflect.Proxy` can't handle overloaded methods. - Google's Dalvik VM can't handle overloaded static fields. `-useuniqueclassmembernames`{: #useuniqueclassmembernames} : Specifies to assign the same obfuscated names to class members that have the same names, and different obfuscated names to class members that have different names (for each given class member signature). Without the option, more class members can be mapped to the same short names like 'a', 'b', etc. The option therefore increases the size of the resulting code slightly, but it ensures that the saved obfuscation name mapping can always be respected in subsequent incremental obfuscation steps. For instance, consider two distinct interfaces containing methods with the same name and signature. Without this option, these methods may get different obfuscated names in a first obfuscation step. If a patch is then added containing a class that implements both interfaces, ProGuard will have to enforce the same method name for both methods in an incremental obfuscation step. The original obfuscated code is changed, in order to keep the resulting code consistent. With this option *in the initial obfuscation step*, such renaming will never be necessary. This option is only applicable when obfuscating. In fact, if you are planning on performing incremental obfuscation, you probably want to avoid shrinking and optimization altogether, since these steps could remove or modify parts of your code that are essential for later additions. `-dontusemixedcaseclassnames`{: #dontusemixedcaseclassnames} : Specifies not to generate mixed-case class names while obfuscating. By default, obfuscated class names can contain a mix of upper-case characters and lower-case characters. This creates perfectly acceptable and usable jars. Only if a jar is unpacked on a platform with a case-insensitive filing system (say, Windows), the unpacking tool may let similarly named class files overwrite each other. Code that self-destructs when it's unpacked! Developers who really want to unpack their jars on Windows can use this option to switch off this behavior. Obfuscated jars will become slightly larger as a result. Only applicable when obfuscating. `-keeppackagenames`{: #keeppackagenames} \[*[package\_filter](#filters)*\] : Specifies not to obfuscate the given package names. The optional filter is a comma-separated list of package names. Package names can contain **?**, **\***, and **\*\*** wildcards, and they can be preceded by the **!** negator. Only applicable when obfuscating. `-flattenpackagehierarchy`{: #flattenpackagehierarchy} \[*package\_name*\] : Specifies to repackage all packages that are renamed, by moving them into the single given parent package. Without argument or with an empty string (''), the packages are moved into the root package. This option is one example of further [obfuscating package names](examples.md#repackaging). It can make the processed code smaller and less comprehensible. Only applicable when obfuscating. `-repackageclasses`{: #repackageclasses} \[*package\_name*\] : Specifies to repackage all class files that are renamed, by moving them into the single given package. Without argument or with an empty string (''), the package is removed completely. This option overrides the [`-flattenpackagehierarchy`](#flattenpackagehierarchy) option. It is another example of further [obfuscating package names](examples.md#repackaging). It can make the processed code even smaller and less comprehensible. Its deprecated name is `-defaultpackage`. Only applicable when obfuscating. *Counter-indication:* classes that look for resource files in their package directories will no longer work properly if they are moved elsewhere. When in doubt, just leave the packaging untouched by not using this option. `-keepattributes`{: #keepattributes} \[*[attribute\_filter](attributes.md)*\] : Specifies any optional attributes to be preserved. The attributes can be specified with one or more [`-keepattributes`](usage.md#keepattributes) directives. The optional filter is a comma-separated list of [attribute names](attributes.md) that Java virtual machines and ProGuard support. Attribute names can contain **?**, **\***, and **\*\*** wildcards, and they can be preceded by the **!** negator. For example, you should at least keep the `Exceptions`, `InnerClasses`, and `Signature` attributes when [processing a library](examples.md#library). You should also keep the `SourceFile` and `LineNumberTable` attributes for [producing useful obfuscated stack traces](examples.md#stacktrace). Finally, you may want to [keep annotations](examples.md#annotations) if your code depends on them. Only applicable when obfuscating. `-keepparameternames`{: #keepparameternames} : Specifies to keep the parameter names and types of methods that are kept. This option actually keeps trimmed versions of the debugging attributes `LocalVariableTable` and `LocalVariableTypeTable`. It can be useful when [processing a library](examples.md#library). Some IDEs can use the information to assist developers who use the library, for example with tool tips or autocompletion. Only applicable when obfuscating. When processing Kotlin metadata the Kotlin function, constructor and property setter parameter names are also kept. `-renamesourcefileattribute`{: #renamesourcefileattribute} \[*string*\] : Specifies a constant string to be put in the `SourceFile` attributes (and `SourceDir` attributes) of the class files. Note that the attribute has to be present to start with, so it also has to be preserved explicitly using the [`-keepattributes`](usage.md#keepattributes) directive. For example, you may want to have your processed libraries and applications produce [useful obfuscated stack traces](examples.md#stacktrace). Only applicable when obfuscating. `-keepkotlinmetadata`{: #keepkotlinmetadata} {: .deprecated} : ** Deprecated: use `-keep class kotlin.Metadata` instead. ** Specifies to process `kotlin.Metadata` annotations if present. Currently only shrinking and obfuscation of its content is supported. Classes containing such annotations should be excuded from optimization if this option is enabled. `-adaptclassstrings`{: #adaptclassstrings} \[*[class\_filter](#filters)*\] : Specifies that string constants that correspond to class names should be obfuscated as well. Without a filter, all string constants that correspond to class names are adapted. With a filter, only string constants in classes that match the filter are adapted. For example, if your code contains a large number of hard-coded strings that refer to classes, and you prefer not to keep their names, you may want to use this option. Primarily applicable when obfuscating, although corresponding classes are automatically kept in the shrinking step too. `-adaptresourcefilenames`{: #adaptresourcefilenames} \[*[file\_filter](#filefilters)*\] : Specifies the resource files to be renamed, based on the obfuscated names of the corresponding class files (if any). Without a filter, all resource files that correspond to class files are renamed. With a filter, only matching files are renamed. For example, see [processing resource files](examples.md#resourcefiles). Only applicable when obfuscating. `-adaptresourcefilecontents`{: #adaptresourcefilecontents} \[*[file\_filter](#filefilters)*\] : Specifies the resource files and native libraries whose contents are to be updated. Any class names mentioned in the resource files are renamed, based on the obfuscated names of the corresponding classes (if any). Any function names in the native libraries are renamed, based on the obfuscated names of the corresponding native methods (if any). Without a filter, the contents of all resource files updated. With a filter, only matching files are updated. The resource files are parsed and written using UTF-8 encoding. For an example, see [processing resource files](examples.md#resourcefiles). Only applicable when obfuscating. *Caveat:* You probably only want to apply this option to text files and native libraries, since parsing and adapting general binary files as text files can cause unexpected problems. Therefore, make sure that you specify a sufficiently narrow filter. ## Preverification Options {: #preverificationoptions} `-dontpreverify`{: #dontpreverify} : Specifies not to preverify the processed class files. By default, class files are preverified if they are targeted at Java Micro Edition or at Java 6 or higher. For Java Micro Edition, preverification is required, so you will need to run an external preverifier on the processed code if you specify this option. For Java 6, preverification is optional, but as of Java 7, it is required. `-microedition`{: #microedition} : Specifies that the processed class files are targeted at Java Micro Edition. The preverifier will then add the appropriate StackMap attributes, which are different from the default StackMapTable attributes for Java Standard Edition. For example, you will need this option if you are [processing midlets](examples.md#midlets). `-android`{: #android} : Specifies that the processed class files are targeted at the Android platform. ProGuard then makes sure some features are compatible with Android. ## General Options {: #generaloptions} `-verbose`{: #verbose} : Specifies to write out some more information during processing. If the program terminates with an exception, this option will print out the entire stack trace, instead of just the exception message. `-dontnote`{: #dontnote} \[*[class\_filter](#filters)*\] : Specifies not to print notes about potential mistakes or omissions in the configuration, such as typos in class names or missing options that might be useful. The optional filter is a regular expression; ProGuard doesn't print notes about classes with matching names. `-dontwarn`{: #dontwarn} \[*[class\_filter](#filters)*\] : Specifies not to warn about unresolved references and other important problems at all. The optional filter is a regular expression; ProGuard doesn't print warnings about classes with matching names. Ignoring warnings can be dangerous. For instance, if the unresolved classes or class members are indeed required for processing, the processed code will not function properly. *Only use this option if you know what you're doing!* `-ignorewarnings`{: #ignorewarnings} : Specifies to print any warnings about unresolved references and other important problems, but to continue processing in any case. Ignoring warnings can be dangerous. For instance, if the unresolved classes or class members are indeed required for processing, the processed code will not function properly. *Only use this option if you know what you're doing!* `-printconfiguration`{: #printconfiguration} \[[*filename*](#filename)\] : Specifies to write out the entire configuration that has been parsed, with included files and replaced variables. The structure is printed to the standard output or to the given file. This can sometimes be useful to debug configurations, or to convert XML configurations into a more readable format. `-dump`{: #dump} \[[*filename*](#filename)\] : Specifies to write out the internal structure of the class files, after any processing. The structure is printed to the standard output or to the given file. For example, you may want to [write out the contents of a given jar file](examples.md#structure), without processing it at all. `-addconfigurationdebugging`{: #addconfigurationdebugging} : Specifies to instrument the processed code with debugging statements that print out suggestions for missing ProGuard configuration. This can be very useful to get practical hints _at run-time_, if your processed code crashes because it still lacks some configuration for reflection. For example, the code may be [serializing classes with the GSON library](examples.md#gson) and you may need some configuration for it. You can generally just copy/paste the suggestions from the console into your configuration file. *Counter-indication:* do not use this option in release versions, as it adds obfuscation information to the processed code. ## Class Paths {: #classpath} ProGuard accepts a generalization of class paths to specify input files and output files. A class path consists of entries, separated by the traditional path separator (e.g. '**:**' on Unix, or '**;**' on Windows platforms). The order of the entries determines their priorities, in case of duplicates. Each input entry can be: - A class file or resource file, - An apk file, containing any of the above, - A jar file, containing any of the above, - An aar file, containing any of the above, - A war file, containing any of the above, - An ear file, containing any of the above, - A jmod file, containing any of the above, - A zip file, containing any of the above, - A directory (structure), containing any of the above. The paths of directly specified class files and resource files is ignored, so class files should generally be part of a jar file, an aar file, a war file, an ear file, a zip file, or a directory. In addition, the paths of class files should not have any additional directory prefixes inside the archives or directories. Each output entry can be: - An apk file, in which all class files and resource files will be collected. - A jar file, in which any and all of the above will be collected, - An aar file, in which any and all of the above will be collected, - A war file, in which any and all of the above will be collected, - An ear file, in which any and all of the above will be collected, - A jmod file, in which any and all of the above will be collected, - A zip file, in which any and all of the above will be collected, - A directory, in which any and all of the above will be collected. When writing output entries, ProGuard generally packages the results in a sensible way, reconstructing the input entries as much as required. Writing everything to an output directory is the most straightforward option: the output directory will contain a complete reconstruction of the input entries. The packaging can be almost arbitrarily complex though: you could process an entire application, packaged in a zip file along with its documentation, writing it out as a zip file again. The Examples section shows a few ways to [restructure output archives](examples.md#restructuring). Files and directories can be specified as discussed in the section on [file names](#filename) below. In addition, ProGuard provides the possibility to filter the class path entries and their contents, based on their full relative file names. Each class path entry can be followed by up to 8 types of [file filters](#filefilters) between parentheses, separated by semi-colons: - A filter for all jmod names that are encountered, - A filter for all aar names that are encountered, - A filter for all apk names that are encountered, - A filter for all zip names that are encountered, - A filter for all ear names that are encountered, - A filter for all war names that are encountered, - A filter for all jar names that are encountered, - A filter for all class file names and resource file names that are encountered. If fewer than 8 filters are specified, they are assumed to be the latter filters. Any empty filters are ignored. More formally, a filtered class path entry looks like this: classpathentry([[[[[[[jmodfilter;]aarfilter;]apkfilter;]zipfilter;]earfilter;]warfilter;]jarfilter;]filefilter) Square brackets "\[\]" mean that their contents are optional. For example, "`rt.jar(java/**.class,javax/**.class)`" matches all class files in the `java` and `javax` directories inside the `rt` jar. For example, "`input.jar(!**.gif,images/**)`" matches all files in the `images` directory inside the `input` jar, except gif files. The different filters are applied to all corresponding file types, irrespective of their nesting levels in the input; they are orthogonal. For example, "`input.war(lib/**.jar,support/**.jar;**.class,**.gif)`" only considers jar files in the `lib` and `support` directories in the `input` war, not any other jar files. It then matches all class files and gif files that are encountered. The filters allow for an almost infinite number of packaging and repackaging possibilities. The Examples section provides a few more examples for [filtering input and output](examples.md#filtering). ## File Names {: #filename} ProGuard accepts absolute paths and relative paths for the various file names and directory names. A relative path is interpreted as follows: - relative to the base directory, if set, or otherwise - relative to the configuration file in which it is specified, if any, or otherwise - relative to the working directory. The names can contain Java system properties (or Ant properties, when using Ant), delimited by angular brackets, '**<**' and '**>**'. The properties are automatically replaced by their corresponding values. For example, `/lib/rt.jar` is automatically expanded to something like `/usr/local/java/jdk/jre/lib/rt.jar`. Similarly, `` is expanded to the user's home directory, and `` is expanded to the current working directory. Names with special characters like spaces and parentheses must be quoted with single or double quotes. Each file name in a list of names has to be quoted individually. Note that the quotes themselves may need to be escaped when used on the command line, to avoid them being gobbled by the shell. For example, on the command line, you could use an option like `'-injars "my program.jar":"/your directory/your program.jar"'`. ## File Filters {: #filefilters} Like general [filters](#filters), a file filter is a comma-separated list of file names that can contain wildcards. Only files with matching file names are read (in the case of input jars), or written (in the case of output jars). The following wildcards are supported: | Wildcard | Meaning |------|----------------------------------------------------------------------------------------- | `?` | matches any single character in a file name. | `*` | matches any part of a filename not containing the directory separator. | `**` | matches any part of a filename, possibly containing any number of directory separators. For example, "`java/**.class,javax/**.class`" matches all class files in the `java` and `javax`. Furthermore, a file name can be preceded by an exclamation mark '**!**' to *exclude* the file name from further attempts to match with *subsequent* file names. For example, "`!**.gif,images/**`" matches all files in the `images` directory, except gif files. The Examples section provides a few more examples for [filtering input and output](examples.md#filtering). ## Filters {: #filters} ProGuard offers options with filters for many different aspects of the configuration: names of files, directories, classes, packages, attributes, optimizations, etc. A filter is a list of comma-separated names that can contain wildcards. Only names that match an item on the list pass the filter. The supported wildcards depend on the type of names for which the filter is being used, but the following wildcards are typical: | Wildcard | Meaning |------|----------------------------------------------------------------------------------------------------------- | `?` | matches any single character in a name. | `*` | matches any part of a name not containing the package separator or directory separator. | `**` | matches any part of a name, possibly containing any number of package separators or directory separators. For example, "`foo,*bar`" matches the name `foo` and all names ending with `bar`. Furthermore, a name can be preceded by a negating exclamation mark '**!**' to *exclude* the name from further attempts to match with *subsequent* names. So, if a name matches an item in the filter, it is accepted or rejected right away, depending on whether the item has a negator. If the name doesn't match the item, it is tested against the next item, and so on. It if doesn't match any items, it is accepted or rejected, depending on the whether the last item has a negator or not. For example, "`!foobar,*bar`" matches all names ending with `bar`, except `foobar`. ## Overview of `Keep` Options {: #keepoverview} The various [`-keep`](usage.md#keep) options for shrinking and obfuscation may seem a bit confusing at first, but there's actually a pattern behind them. The following table summarizes how they are related: | Keep | From being removed or renamed | From being renamed |-----------------------------------------------------|------------------------------------------------------|-------------------------------------------------------------- | Classes and class members | [`-keep`](#keep) | [`-keepnames`](#keepnames) | Class members only | [`-keepclassmembers`](#keepclassmembers) | [`-keepclassmembernames`](#keepclassmembernames) | Classes and class members, if class members present | [`-keepclasseswithmembers`](#keepclasseswithmembers) | [`-keepclasseswithmembernames`](#keepclasseswithmembernames) Each of these [`-keep`](usage.md#keep) options is of course followed by a [specification](#classspecification) of the classes and class members (fields and methods) to which it should be applied. If you're not sure which option you need, you should probably simply use `-keep`. It will make sure the specified classes and class members are not removed in the shrinking step, and not renamed in the obfuscation step. !!! warning "" - If you specify a class, without class members, ProGuard only preserves the class and its parameterless constructor as entry points. It may still remove, optimize, or obfuscate its other class members. - If you specify a method, ProGuard only preserves the method as an entry point. Its code may still be optimized and adapted. ## Keep Option Modifiers {: #keepoptionmodifiers} `includedescriptorclasses` : Specifies that any classes in the type descriptors of the methods and fields that the [-keep](#keep) option keeps should be kept as well. This is typically useful when [keeping native method names](examples.md#native), to make sure that the parameter types of native methods aren't renamed either. Their signatures then remain completely unchanged and compatible with the native libraries. `includecode` : Specifies that code attributes of the methods that the [-keep](#keep) option keeps should be kept as well, i.e. may not be optimized or obfuscated. This is typically useful for already optimized or obfuscated classes, to make sure that their code is not modified during optimization. `allowshrinking` : Specifies that the entry points specified in the [-keep](#keep) option may be shrunk, even if they have to be preserved otherwise. That is, the entry points may be removed in the shrinking step, but if they are necessary after all, they may not be optimized or obfuscated. `allowoptimization` : Specifies that the entry points specified in the [-keep](#keep) option may be optimized, even if they have to be preserved otherwise. That is, the entry points may be altered in the optimization step, but they may not be removed or obfuscated. This modifier is only useful for achieving unusual requirements. `allowobfuscation` : Specifies that the entry points specified in the [-keep](#keep) option may be obfuscated, even if they have to be preserved otherwise. That is, the entry points may be renamed in the obfuscation step, but they may not be removed or optimized. This modifier is only useful for achieving unusual requirements. ## Class Specifications {: #classspecification} A class specification is a template of classes and class members (fields and methods). It is used in the various [`-keep`](usage.md#keep) options and in the `-assumenosideeffects` option. The corresponding option is only applied to classes and class members that match the template. The template was designed to look very Java-like, with some extensions for wildcards. To get a feel for the syntax, you should probably look at the [examples](examples.md), but this is an attempt at a complete formal definition: [@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname [extends|implements [@annotationtype] classname] [{ [@annotationtype] [[!]public|private|protected|static|volatile|transient ...] | (fieldtype fieldname [= values]); [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] | (argumenttype,...) | classname(argumenttype,...) | (returntype methodname(argumenttype,...) [return values]); }] Square brackets "\[\]" mean that their contents are optional. Ellipsis dots "..." mean that any number of the preceding items may be specified. A vertical bar "|" delimits two alternatives. Non-bold parentheses "()" just group parts of the specification that belong together. The indentation tries to clarify the intended meaning, but white-space is irrelevant in actual configuration files. - The `class` keyword refers to any interface or class. The `interface` keyword restricts matches to interface classes. The `enum` keyword restricts matches to enumeration classes. Preceding the `interface` or `enum` keywords by a `!` restricts matches to classes that are not interfaces or enumerations, respectively. - Every *classname* must be fully qualified, e.g. `java.lang.String`. Inner classes are separated by a dollar sign "`$`", e.g. `java.lang.Thread$State`. Class names may be specified as regular expressions containing the following wildcards: | Wildcard | Meaning |-------|----------- | `?` | matches any single character in a class name, but not the package separator. For example, "`com.example.Test?`" matches "`com.example.Test1`" and "`com.example.Test2`", but not "`com.example.Test12`". | `*` | matches any part of a class name not containing the package separator. For example, "`com.example.*Test*`" matches "`com.example.Test`" and "`com.example.YourTestApplication`", but not "`com.example.mysubpackage.MyTest`". Or, more generally, "`com.example.*`" matches all classes in "`com.example`", but not in its subpackages. | `**` | matches any part of a class name, possibly containing any number of package separators. For example, "`**.Test`" matches all `Test` classes in all packages except the root package. Or, "`com.example.**`" matches all classes in "`com.example`" and in its subpackages. | `` | matches the _n_'th matched wildcard in the same option. For example, "`com.example.*Foo<1>`" matches "`com.example.BarFooBar`". For additional flexibility, class names can actually be comma-separated lists of class names, with optional `!` negators, just like file name filters. This notation doesn't look very Java-like, so it should be used with moderation. For convenience and for backward compatibility, the class name `*` refers to any class, irrespective of its package, when used on its own (e.g. `-keep class *`). - The `extends` and `implements` specifications are typically used to restrict classes with wildcards. They are currently equivalent, specifying that only classes extending or implementing the given class (directly or indirectly) qualify. The given class itself is not included in this set. If required, it should be specified in a separate option. - The `@` specifications can be used to restrict classes and class members to the ones that are annotated with the specified annotation types. An *annotationtype* is specified just like a *classname*. - Fields and methods are specified much like in Java, except that method argument lists don't contain argument names (just like in other tools like `javadoc` and `javap`). The specifications can also contain the following catch-all wildcards: | Wildcard | Meaning |-------------|------------------------------ | `` | matches any constructor. | `` | matches any field. | `` | matches any method. | `*` | matches any field or method. Note that the above wildcards don't have return types. Only the `` wildcard has an argument list. Fields and methods may also be specified using regular expressions. Names can contain the following wildcards: | Wildcard | Meaning |-------|----------------------------------------------------------------- | `?` | matches any single character in a method name. | `*` | matches any part of a method name. | `` | matches the _n_'th matched wildcard in the same option. Types in descriptors can contain the following wildcards: | Wildcard | Meaning |-------|----------------------------------------------------------------------------------------- | `%` | matches any primitive type ("`boolean`", "`int`", etc) or "`void`" type. | `?` | matches any single character in a class name. | `*` | matches any part of a class name not containing the package separator. | `**` | matches any part of a class name, possibly containing any number of package separators. | `***` | matches any type (primitive or non-primitive, array or non-array). | `...` | matches any number of arguments of any type. | `` | matches the _n_'th matched wildcard in the same option. Note that the `?`, `*`, and `**` wildcards will never match primitive types. Furthermore, only the `***` wildcards will match array types of any dimension. For example, "`** get*()`" matches "`java.lang.Object getObject()`", but not "`float getFloat()`", nor "`java.lang.Object[] getObjects()`". - Constructors can also be specified using their short class names (without package) or using their full class names. As in the Java language, the constructor specification has an argument list, but no return type. - The class access modifiers and class member access modifiers are typically used to restrict wildcarded classes and class members. They specify that the corresponding access flags have to be set for the member to match. A preceding `!` specifies that the corresponding access flag should be unset. Combining multiple flags is allowed (e.g. `public static`). It means that both access flags have to be set (e.g. `public` *and* `static`), except when they are conflicting, in which case at least one of them has to be set (e.g. at least `public` *or* `protected`). ProGuard supports the additional modifiers `synthetic`, `bridge`, and `varargs`, which may be set by compilers. - With the option [`-assumevalues`](#assumevalues), fields and methods with primitive return types can have *values* or *ranges of values*. The assignment keyword is `=` or `return`, interchangeably. For example, "`boolean flag = true;`" or "`int method() return 5;`". Ranges of values are separated by `..`, for example, "`int f = 100..200;`". A range includes its begin value and end value. ================================================ FILE: docs/md/manual/feedback.md ================================================ By now, we've invested an enormous amount of time in **ProGuard**. You can help by providing feedback! If you have problems, bugs, bug fixes, ideas, encouragements, etc., please let us know. At [Guardsquare](http://www.guardsquare.com/), we develop ProGuard and its professional siblings DexGuard (for Android) and iXGuard (for iOS). If you find ProGuard useful and you are interested in more features or professional support, this is the place to go. ProGuard is currently hosted on GitHub: - You can report issues on our [issue tracker](https://github.com/Guardsquare/proguard/issues). - You can clone the [source code](https://github.com/Guardsquare/proguard) yourself and create [pull requests](https://github.com/Guardsquare/proguard/pulls). !!! tip The [***Guardsquare Community***](https://community.guardsquare.com/) is the place to be for all your ProGuard-related questions and feedback. You may also find answers on [Stack Overflow](http://stackoverflow.com/questions/tagged/proguard). ProGuard used to be hosted on Sourceforge: - You can still read answers in the [help forum](https://sourceforge.net/projects/proguard/forums/forum/182456). - You can still find discussions in the [open discussion forum](https://sourceforge.net/projects/proguard/forums/forum/182455). - We still have reports on the [bug tracking page](http://sourceforge.net/p/proguard/bugs/). - We still have new ideas on the the [feature request page](http://sourceforge.net/p/proguard/feature-requests/). - You can still find all earlier versions in the [download section](https://sourceforge.net/projects/proguard/files/). ================================================ FILE: docs/md/manual/home.md ================================================ Welcome to the manual for **ProGuard** version 7.3 ([what's new?](releasenotes.md)). ProGuard is an open-sourced Java class file shrinker, optimizer, obfuscator, and preverifier. As a result, ProGuard processed applications and libraries are smaller and faster. - The ***shrinking step*** detects and removes unused classes, fields, methods, and attributes. - The ***optimizer step*** optimizes bytecode and removes unused instructions. - The ***name obfuscation step*** renames the remaining classes, fields, and methods using short meaningless names. - The final ***preverification step*** adds preverification information to the classes, which is required for Java Micro Edition and for Java 6 and higher. The default Android shrinker, R8, is compatible with ProGuard [configuration](configuration/usage.md). If you are getting started with ProGuard, please follow the [Quick Start](building.md) guide in order to arrive at a basic setup for your application or library as quickly as possible. Experienced users can directly consult the [Configuration section](configuration/usage.md) where all features are described. If during the process you run into any issues, please make sure to check the [Troubleshooting section](troubleshooting/troubleshooting.md). ## How it works
Input jars
shrink
optimize
obfuscate
preverify
Output jars
Library jars
(unchanged)
Library jars
ProGuard first reads the **input jars** (or aars, wars, ears, zips, apks, or directories). It then subsequently shrinks, optimizes, obfuscates, and preverifies them. You can optionally let ProGuard perform multiple optimization passes. ProGuard writes the processed results to one or more **output jars** (or aars, wars, ears, zips, apks, or directories). The input may contain resource files, whose names and contents can optionally be updated to reflect the obfuscated class names. ProGuard requires the **library jars** (or aars, wars, ears, zips, apks, or directories) of the input jars to be specified. These are essentially the libraries that you would need for compiling the code. ProGuard uses them to reconstruct the class dependencies that are necessary for proper processing. The library jars themselves always remain unchanged. You should still put them in the class path of your final application. ## Entry points In order to determine which code has to be preserved and which code can be discarded or obfuscated, you have to specify one or more *entry points* to your code. These entry points are typically classes with main methods, applets, midlets, activities, etc. - In the **shrinking step**, ProGuard starts from these seeds and recursively determines which classes and class members are used. All other classes and class members are discarded. - In the **optimization step**, ProGuard further optimizes the code. Among other optimizations, classes and methods that are not entry points can be made private, static, or final, unused parameters can be removed, and some methods may be inlined. - In the **name obfuscation step**, ProGuard renames classes and class members that are not entry points. In this entire process, keeping the entry points ensures that they can still be accessed by their original names. - The **preverification step** is the only step that doesn't have to know the entry points. The [Usage section](configuration/usage.md) of this manual describes the necessary [`-keep` options](configuration/usage.md#keepoptions) and the [Examples section](configuration/examples.md) provides plenty of examples. ## Reflection Reflection and introspection present particular problems for any automatic processing of code. In ProGuard, classes or class members in your code that are created or invoked dynamically (that is, by name) have to be specified as entry points too. For example, `Class.forName()` constructs may refer to any class at run-time. It is generally impossible to compute which classes have to be preserved (with their original names), since the class names might be read from a configuration file, for instance. You therefore have to specify them in your ProGuard configuration, with the same simple [`-keep`](configuration/usage.md#keep) options. However, ProGuard already detects and handles the following cases for you: - `Class.forName("SomeClass")` - `SomeClass.class` - `SomeClass.class.getField("someField")` - `SomeClass.class.getDeclaredField("someField")` - `SomeClass.class.getMethod("someMethod", null)` - `SomeClass.class.getMethod("someMethod", new Class[] { A.class,... })` - `SomeClass.class.getDeclaredMethod("someMethod", null)` - `SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class,... })` - `AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")` - `AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")` - `AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")` The names of the classes and class members may of course be different, but the constructs should be literally the same for ProGuard to recognize them. The referenced classes and class members are preserved in the shrinking phase, and the string arguments are properly updated in the obfuscation phase. Furthermore, ProGuard will offer some suggestions if keeping some classes or class members appears necessary. For example, ProGuard will note constructs like "`(SomeClass)Class.forName(variable).newInstance()`". These might be an indication that the class or interface `SomeClass` and/or its implementations may need to be preserved. You can then adapt your configuration accordingly. !!! tip Generate an instrumented build to allow ProGuard finding cases of reflection at *run-time*. The tailored configuration advice for your application will be outputted to the console, and can be copy/pasted to your configuration. To do so, just enable the option [`-addconfigurationdebugging`](configuration/usage.md#addconfigurationdebugging) For proper results, you should at least be somewhat familiar with the code that you are processing. Obfuscating code that performs a lot of reflection may require trial and error, especially without the necessary information about the internals of the code. ================================================ FILE: docs/md/manual/languages/java.md ================================================ ## Java language support !!! Warning ProGuard no longer supports backporting, and cannot backport class files compiled with Java >11. Provide supports Java versions up to and including 19. ================================================ FILE: docs/md/manual/languages/kotlin.md ================================================ The Kotlin compiler injects code and metadata into the classes that it generates to support features not natively supported by the Java and Android environments. The metadata injected by the Kotlin compiler takes the shape of an annotation added to classes which leaks semantic information that can aid attackers. ## Configuration In most cases, you do not need to keep Kotlin metadata for app projects - therefore, no configuration changes are necessary and the Kotlin metadata can be safely removed. However, there are two common reasons to explicitly keep the metadata, [reflection](#reflection) and [libraries](#library-projects). ProGuard will only keep the Kotlin metadata of a class if you explicitly keep that class or one of its members, and you add the `-keep class kotlin.Metadata` option to your configuration. Note that this option may also be required by an SDK of your project, and indirectly added as a consumer rule. For example, if you have the following keep rule for a Kotlin class named `com.example.KotlinExample`, by default the class will be kept but its metadata will not: ``` # Keep the class com.example.KotlinExample -keep class com.example.KotlinExample ``` You can add `-keep class kotlin.Metadata` to your configuration to instruct ProGuard to keep and adapt Kotlin metadata: ``` # Add this option to tell ProGuard to keep and adapt Kotlin metadata -keep class kotlin.Metadata ``` ### App Projects The most common case to keep Kotlin metadata would be if you use the [kotlin-reflect](https://kotlinlang.org/docs/reference/reflection.html) library. Just like when using Java reflection, you will need `-keep` rules in your configuration to keep the specific classes and members accessed through reflection. In this case, to instruct ProGuard to keep and adapt the corresponding Kotlin metadata, add the following to your configuration: ``` -keep class kotlin.Metadata ``` A popular framework that relies on reflection is [Jackson](https://github.com/FasterXML/jackson-module-kotlin). ### Library Projects When developing an SDK that exposes Kotlin-specific features to its users, you need to preserve the metadata of the public API. These include features such as named parameters, suspend functions, top-level functions and type aliases. In the case of a library, you would already be keeping the public API, so you can simply add the following to your configuration: ``` -keep class kotlin.Metadata ``` ## Protection ### Obfuscation ProGuard will apply the same obfuscations to Kotlin identifiers in metadata, such as class or member names, to match those in the Java class files. This ensures that, even where the Kotlin metadata is required, no sensitive names remain in the metadata. ### Shrinking ProGuard will remove all unused Kotlin metadata components such as unused functions and properties. This ensures that, even where the Kotlin metadata is required, only the used components are kept. ### Data Classes Data classes in Kotlin have an auto-generated `toString` method that lists all properties of the class and their value. ProGuard automatically detects these classes and adapts the names of properties to their obfuscated counterpart. ### Intrinsics Null Checks To better support java interoperability, Kotlin injects numerous method calls to e.g. check that a parameter is not null when it wasn't marked as `Nullable`. In pure Kotlin codebases, these injected method calls are unnecessary and instead leak information via their parameters (e.g. names of checked parameters). ProGuard automatically detects calls to these methods and removes the Strings to ensure that the resulting code contains no references to original parameter names, member names etc. ================================================ FILE: docs/md/manual/license/gpl.md ================================================ # GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. ## Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. ## TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION **0.** This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. **1.** You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. **2.** You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: - **a)** You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. - **b)** You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. - **c)** If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. **3.** You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: - **a)** Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, - **b)** Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, - **c)** Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. **4.** You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. **5.** You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. **6.** Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. **7.** If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. **8.** If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. **9.** The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. **10.** If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. **NO WARRANTY** **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. ================================================ FILE: docs/md/manual/license/gplexception.md ================================================ # Special Exception to the GNU General Public License Copyright © 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 In addition, as a special exception, Guardsquare NV gives permission to link the code of this program with the following stand-alone applications: - Gradle, - Apache Ant, - Apache Maven, - the Google Android SDK, - the Intel TXE/DAL SDK, - the Eclipse ProGuardDT GUI, - the EclipseME JME IDE, - the Oracle NetBeans Java IDE, - the Oracle JME Wireless Toolkit, and - the Simple Build Tool for Scala (and its scripts). and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than these programs. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. ================================================ FILE: docs/md/manual/license/license.md ================================================ **ProGuard** is free. You can use it freely for processing your applications, commercial or not. Your code obviously remains yours after having been processed, and its license can remain unchanged. The **ProGuard code** itself is copyrighted, but its distribution license provides you with some rights for modifying and redistributing its code and its documentation. More specifically, ProGuard is distributed under the terms of the [GNU General Public License](gpl.md) (GPL), version 2, as published by the [Free Software Foundation](http://www.fsf.org/) (FSF). In short, this means that you may freely redistribute the program, modified or as is, on the condition that you make the complete source code available as well. If you develop a program that is linked with ProGuard, the program as a whole has to be distributed at no charge under the GPL. We are granting a [special exception](gplexception.md) to the latter clause (in wording suggested by the [FSF](http://www.gnu.org/copyleft/gpl-faq.html#GPLIncompatibleLibs)), for combinations with the following stand-alone applications: Gradle, Apache Ant, Apache Maven, the Google Android SDK, the Intel TXE/DAL SDK, the Eclipse ProGuardDT GUI, the EclipseME JME IDE, the Oracle NetBeans Java IDE, the Oracle JME Wireless Toolkit, and the Simple Build Tool for Scala. The **ProGuard user documentation** is copyrighted as well. It may only be redistributed without changes, along with the unmodified version of the code. ================================================ FILE: docs/md/manual/quickstart.md ================================================ # Quick Start This page will guide you through the basic steps of processing your application or library with ProGuard. For details on advanced settings or more background information please refer to the relevant parts of the manual. There are two ways to execute ProGuard: 1. Standalone 2. Integrated mode in your Gradle, Ant or Maven project. You can also build ProGuard from [source](https://github.com/Guardsquare/proguard) by following the [build instructions](building.md). ## Standalone Firstly, download a [ProGuard release](https://github.com/Guardsquare/proguard/releases) or [build ProGuard](building.md) from source. ProGuard can then be executed directly from the command line by calling a script found in the `bin` directory: === "Linux/macOS" ```bash bin/proguard.sh -injars path/to/my-application.jar \ -outjars path/to/obfuscated-application.jar \ -libraryjars path/to/java/home/lib/rt.jar ``` === "Windows" ```bat bin\proguard.bat -injars path/to/my-application.jar ^ -outjars path/to/obfuscated-application.jar ^ -libraryjars path/to/java/home/lib/rt.jar ``` For more detailed information see [standalone mode](setup/standalone.md). ## Integrated The ProGuard artifacts are hosted at [Maven Central](https://search.maven.org/search?q=g:com.guardsquare). ### Android Gradle project When working on your Android application (apk, aab) or library (aar), you can include ProGuard in your Gradle build by: - Using ProGuard's Gradle plugin, which you can apply in your `build.gradle` file (AGP 4.x - 7.x). - Using the integrated ProGuard by disabling R8 in your `gradle.properties` (only applicable for AGP < 7). For more detailed information see [Android Gradle](setup/gradleplugin.md). ### Java or Kotlin Gradle project Your non-mobile Java or Kotlin applications can execute ProGuard's Gradle task: ```proguard task myProguardTask(type: proguard.gradle.ProGuardTask) { ..... } ``` For more detailed information see [Java/Kotlin Gradle](setup/gradle.md). ### Ant project You can also include ProGuard in your Ant build, all you have to do is to include the related task into your `build.xml` file: ```xml ``` For more detailed information see [Ant](setup/ant.md). ### Maven project !!! warning While we don't officially provide a maven integration and we cannot provide support there are solutions available, their offered functionality is not guaranteed by Guardsquare. Some open-source implementations: - [https://github.com/wvengen/proguard-maven-plugin](https://github.com/wvengen/proguard-maven-plugin) - [https://github.com/dingxin/proguard-maven-plugin](https://github.com/dingxin/proguard-maven-plugin) ================================================ FILE: docs/md/manual/refcard.md ================================================ ## Usage | OS | Command |------------|----------------------------- | Windows: | `proguard` *options* ... | Linux/Mac: | `proguard.sh` *options* ... Typically: | OS | Command |------------|----------------------------- | Windows: | `proguard @myconfig.pro` | Linux/Mac: | `proguard.sh @myconfig.pro` ## Options | Option | Meaning |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------- | [`@`](configuration/usage.md#at)[*filename*](configuration/usage.md#filename) | Short for '`-include` *filename*'. | [`-include`](configuration/usage.md#include) [*filename*](configuration/usage.md#filename) | Read configuration options from the given file. | [`-basedirectory`](configuration/usage.md#basedirectory) [*directoryname*](configuration/usage.md#filename) | Specifies the base directory for subsequent relative file names. | [`-injars`](configuration/usage.md#injars) [*class\_path*](configuration/usage.md#classpath) | Specifies the program jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). | [`-outjars`](configuration/usage.md#outjars) [*class\_path*](configuration/usage.md#classpath) | Specifies the names of the output jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). | [`-libraryjars`](configuration/usage.md#libraryjars) [*class\_path*](configuration/usage.md#classpath) | Specifies the library jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). | [`-skipnonpubliclibraryclasses`](configuration/usage.md#skipnonpubliclibraryclasses) | Ignore non-public library classes. | [`-dontskipnonpubliclibraryclasses`](configuration/usage.md#dontskipnonpubliclibraryclasses) | Don't ignore non-public library classes (the default). | [`-dontskipnonpubliclibraryclassmembers`](configuration/usage.md#dontskipnonpubliclibraryclassmembers) | Don't ignore package visible library class members. | [`-keepdirectories`](configuration/usage.md#keepdirectories) \[[*directory\_filter*](configuration/usage.md#filters)\] | Keep the specified directories in the output jars (or wars, ears, zips, or directories). | [`-target`](configuration/usage.md#target) *version* | **deprecated** Set the given version number in the processed classes. | [`-forceprocessing`](configuration/usage.md#forceprocessing) | Process the input, even if the output seems up to date. | [`-keep`](configuration/usage.md#keep) \[[,*modifier*](configuration/usage.md#keepoptionmodifiers),...\] [*class\_specification*](configuration/usage.md#classspecification) | Preserve the specified classes *and* class members. | [`-keepclassmembers`](configuration/usage.md#keepclassmembers) \[[,*modifier*](configuration/usage.md#keepoptionmodifiers),...\] [*class\_specification*](configuration/usage.md#classspecification) | Preserve the specified class members, if their classes are preserved as well. | [`-keepclasseswithmembers`](configuration/usage.md#keepclasseswithmembers) \[[,*modifier*](configuration/usage.md#keepoptionmodifiers),...\] [*class\_specification*](configuration/usage.md#classspecification) | Preserve the specified classes *and* class members, if all of the specified class members are present. | [`-keepnames`](configuration/usage.md#keepnames) [*class\_specification*](configuration/usage.md#classspecification) | Preserve the names of the specified classes *and* class members (if they aren't removed in the shrinking step). | [`-keepclassmembernames`](configuration/usage.md#keepclassmembernames) [*class\_specification*](configuration/usage.md#classspecification) | Preserve the names of the specified class members (if they aren't removed in the shrinking step). | [`-keepclasseswithmembernames`](configuration/usage.md#keepclasseswithmembernames) [*class\_specification*](configuration/usage.md#classspecification) | Preserve the names of the specified classes *and* class members, if all of the specified class members are present (after the shrinking step). | [`-if`](configuration/usage.md#if) [*class\_specification*](configuration/usage.md#classspecification) | Specify classes and class members that must be present to activate the subsequent `keep` option. | [`-printseeds`](configuration/usage.md#printseeds) \[[*filename*](configuration/usage.md#filename)\] | List classes and class members matched by the various [`-keep`](configuration/usage.md#keep) options, to the standard output or to the given file. | [`-dontshrink`](configuration/usage.md#dontshrink) | Don't shrink the input class files. | [`-printusage`](configuration/usage.md#printusage) \[[*filename*](configuration/usage.md#filename)\] | List dead code of the input class files, to the standard output or to the given file. | [`-whyareyoukeeping`](configuration/usage.md#whyareyoukeeping) [*class\_specification*](configuration/usage.md#classspecification) | Print details on why the given classes and class members are being kept in the shrinking step. | [`-dontoptimize`](configuration/usage.md#dontoptimize) | Don't optimize the input class files. | [`-optimizations`](configuration/usage.md#optimizations) [*optimization\_filter*](configuration/optimizations.md) | The optimizations to be enabled and disabled. | [`-optimizationpasses`](configuration/usage.md#optimizationpasses) *n* | The number of optimization passes to be performed. | [`-assumenosideeffects`](configuration/usage.md#assumenosideeffects) [*class\_specification*](configuration/usage.md#classspecification) | Assume that the specified methods don't have any side effects, while optimizing. | [`-assumenoexternalsideeffects`](configuration/usage.md#assumenoexternalsideeffects) [*class\_specification*](configuration/usage.md#classspecification) | Assume that the specified methods don't have any external side effects, while optimizing. | [`-assumenoescapingparameters`](configuration/usage.md#assumenoescapingparameters) [*class\_specification*](configuration/usage.md#classspecification) | Assume that the specified methods don't let any reference parameters escape to the heap, while optimizing. | [`-assumenoexternalreturnvalues`](configuration/usage.md#assumenoexternalreturnvalues) [*class\_specification*](configuration/usage.md#classspecification) | Assume that the specified methods don't return any external reference values, while optimizing. | [`-assumevalues`](configuration/usage.md#assumevalues) [*class\_specification*](configuration/usage.md#classspecification) | Assume fixed values or ranges of values for primitive fields and methods, while optimizing. | [`-allowaccessmodification`](configuration/usage.md#allowaccessmodification) | Allow the access modifiers of classes and class members to be modified, while optimizing. | [`-mergeinterfacesaggressively`](configuration/usage.md#mergeinterfacesaggressively) | Allow any interfaces to be merged, while optimizing. | [`-dontobfuscate`](configuration/usage.md#dontobfuscate) | Don't obfuscate the input class files. | [`-printmapping`](configuration/usage.md#printmapping) \[[*filename*](configuration/usage.md#filename)\] | Print the mapping from old names to new names for classes and class members that have been renamed, to the standard output or to the given file. | [`-applymapping`](configuration/usage.md#applymapping) [*filename*](configuration/usage.md#filename) | Reuse the given mapping, for incremental obfuscation. | [`-obfuscationdictionary`](configuration/usage.md#obfuscationdictionary) [*filename*](configuration/usage.md#filename) | Use the words in the given text file as obfuscated field names and method names. | [`-classobfuscationdictionary`](configuration/usage.md#classobfuscationdictionary) [*filename*](configuration/usage.md#filename) | Use the words in the given text file as obfuscated class names. | [`-packageobfuscationdictionary`](configuration/usage.md#packageobfuscationdictionary) [*filename*](configuration/usage.md#filename) | Use the words in the given text file as obfuscated package names. | [`-overloadaggressively`](configuration/usage.md#overloadaggressively) | Apply aggressive overloading while obfuscating. | [`-useuniqueclassmembernames`](configuration/usage.md#useuniqueclassmembernames) | Ensure uniform obfuscated class member names for subsequent incremental obfuscation. | [`-dontusemixedcaseclassnames`](configuration/usage.md#dontusemixedcaseclassnames) | Don't generate mixed-case class names while obfuscating. | [`-keeppackagenames`](configuration/usage.md#keeppackagenames) \[*[package\_filter](configuration/usage.md#filters)*\] | Keep the specified package names from being obfuscated. | [`-flattenpackagehierarchy`](configuration/usage.md#flattenpackagehierarchy) \[*package\_name*\] | Repackage all packages that are renamed into the single given parent package. | [`-repackageclasses`](configuration/usage.md#repackageclasses) \[*package\_name*\] | Repackage all class files that are renamed into the single given package. | [`-keepattributes`](configuration/usage.md#keepattributes) \[*[attribute\_filter](configuration/usage.md#filters)*\] | Preserve the given optional attributes; typically `Exceptions`, `InnerClasses`, `Signature`, `Deprecated`, `SourceFile`, `SourceDir`, `LineNumberTable`, `LocalVariableTable`, `LocalVariableTypeTable`, `Synthetic`, `EnclosingMethod`, and `*Annotation*`. | [`-keepparameternames`](configuration/usage.md#keepparameternames) | Keep the parameter names and types of methods that are kept. | [`-renamesourcefileattribute`](configuration/usage.md#renamesourcefileattribute) \[*string*\] | Put the given constant string in the `SourceFile` attributes. | [`-adaptclassstrings`](configuration/usage.md#adaptclassstrings) \[[*class\_filter*](configuration/usage.md#filters)\] | Adapt string constants in the specified classes, based on the obfuscated names of any corresponding classes. | [`-keepkotlinmetadata`](configuration/usage.md#keepkotlinmetadata) ** deprecated ** | Keep and adapt Kotlin metadata. | [`-adaptresourcefilecontents`](configuration/usage.md#adaptresourcefilecontents) \[[*file\_filter*](configuration/usage.md#filefilters)\] | Update the contents of the specified resource files, based on the obfuscated names of the processed classes. | [`-dontpreverify`](configuration/usage.md#dontpreverify) | Don't preverify the processed class files. | [`-microedition`](configuration/usage.md#microedition) | Target the processed class files at Java Micro Edition. | [`-android`](configuration/usage.md#android) | Target the processed class files at Android. | [`-verbose`](configuration/usage.md#verbose) | Write out some more information during processing. | [`-dontnote`](configuration/usage.md#dontnote) \[[*class\_filter*](configuration/usage.md#filters)\] | Don't print notes about potential mistakes or omissions in the configuration. | [`-dontwarn`](configuration/usage.md#dontwarn) \[[*class\_filter*](configuration/usage.md#filters)\] | Don't warn about unresolved references at all. | [`-ignorewarnings`](configuration/usage.md#ignorewarnings) | Print warnings about unresolved references, but continue processing anyhow. | [`-printconfiguration`](configuration/usage.md#printconfiguration) \[[*filename*](configuration/usage.md#filename)\] | Write out the entire configuration, in traditional ProGuard style, to the standard output or to the given file. | [`-dump`](configuration/usage.md#dump) \[[*filename*](configuration/usage.md#filename)\] | Write out the internal structure of the processed class files, to the standard output or to the given file. | [`-addconfigurationdebugging`](configuration/usage.md#addconfigurationdebugging) | Instrument the processed code with debugging statements that print out suggestions for missing ProGuard configuration. | [`-optimizeaggressively`](configuration/usage.md#optimizeaggressively) | Enables more aggressive assumptions during optimization Notes: - *class\_path* is a list of jars, apks, aabs, aars, wars, ears, jmods, zips, and directories, with optional filters, separated by path separators. - *filename* can contain Java system properties delimited by '**<**' and '**>**'. - If *filename* contains special characters, the entire name should be quoted with single or double quotes. ## Overview of `Keep` Options {: #keepoverview} | Keep | From being removed or renamed | From being renamed |-----------------------------------------------------|--------------------------------------------------------------|------------------------------------------------------------------------ | Classes and class members | [`-keep`](configuration/usage.md#keep) | [`-keepnames`](configuration/usage.md#keepnames) | Class members only | [`-keepclassmembers`](configuration/usage.md#keepclassmembers) | [`-keepclassmembernames`](configuration/usage.md#keepclassmembernames) | Classes and class members, if class members present | [`-keepclasseswithmembers`](configuration/usage.md#keepclasseswithmembers) | [`-keepclasseswithmembernames`](configuration/usage.md#keepclasseswithmembernames) The [**ProGuard Playground**](https://playground.proguard.com) is a useful tool to help you further tweak the keep rules. ## Keep Option Modifiers {: #keepoptionmodifiers} | Modifier | Meaning |-----------------------------------------------------------------|--------------------------------------------------------------------------- | [`includedescriptorclasses`](configuration/usage.md#includedescriptorclasses) | Also keep any classes in the descriptors of specified fields and methods. | [`includecode`](configuration/usage.md#includecode) | Also keep the code of the specified methods unchanged. | [`allowshrinking`](configuration/usage.md#allowshrinking) | Allow the specified entry points to be removed in the shrinking step. | [`allowoptimization`](configuration/usage.md#allowoptimization) | Allow the specified entry points to be modified in the optimization step. | [`allowobfuscation`](configuration/usage.md#allowobfuscation) | Allow the specified entry points to be renamed in the obfuscation step. ## Class Specifications {: #classspecification} [@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname [extends|implements [@annotationtype] classname] [{ [@annotationtype] [[!]public|private|protected|static|volatile|transient ...] | (fieldtype fieldname [= values]); [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] | (argumenttype,...) | classname(argumenttype,...) | (returntype methodname(argumenttype,...)); [@annotationtype] [[!]public|private|protected|static ... ] *; ... }] Notes: - Class names must always be fully qualified, i.e. including their package names. - Types in *classname*, *annotationtype*, *returntype*, and *argumenttype* can contain wildcards: '`?`' for a single character, '`*`' for any number of characters (but not the package separator), '`**`' for any number of (any) characters, '`%`' for any primitive type, '`***`' for any type, '`...`' for any number of arguments, and '``' for the *n*'th matched wildcard in the same option. - *fieldname* and *methodname* can contain wildcards as well: '`?`' for a single character and '`*`' for any number of characters. ================================================ FILE: docs/md/manual/releasenotes.md ================================================ ## Version 7.3.3 ### Bugfixes - Fix "NoClassDefFoundError: Failed resolution of: Lorg/apache/logging/log4j/LogManager" when using GSON optimization or `-addconfigurationdebugging`. (#326) ## Version 7.3.2 ### Java support - Add support for Java 20. (#294) ### Improved - Merge classes only when `-optimizeaggressively` is set. ### Bugfixes - Fix potential `ArrayIndexOutOfBoundsException` when processing Kotlin metadata. (#297) ## Version 7.3.1 ### Kotlin support - Add support for Kotlin 1.8. ### Improved - Conservative optimization is now the default. Previously, it could be enabled by setting the `optimize.conservatively` system property. This has been replaced with the `-optimizeaggressively` option, which sets optimization to aggressive. - Improve optimization performance in edge cases with generated code. (#283) ### Bugfixes - Fix `-keepparameternames` to keep Kotlin function, constructor and property setter parameter names. - Fix `-keepparameternames` to keep Kotlin annotation constructor parameter names. - Fix `-keepparameternames` to keep Kotlin interface parameter names. - Fix potential `NullPointerException` while processing enum classes with invalid Kotlin metadata. - Fix potential `Instruction has invalid constant index size` error during GSON optimization. - Fix member specialization & generalization optimizations. - Fix potential "Сan't find referenced class ClassName$DefaultImpls" warnings. (#290) ## Version 7.3.0 ### Java support To allow ProGuard to continue to optimize, obfuscate and shrink Java class files ProGuard now supports all Java versions including Java 19. - Add support for Java 19. (`PGD-247`) ### Kotlin support ProGuard 7.3 deprecates the `-keepkotlinmetadata` option. You can use `-keep class kotlin.Metadata` instead which automatically enables processing of Kotlin metadata. Some consumer rules, from libraries such as `kotlin-reflect`, already contain this rule. - Add support for Kotlin 1.7. - Improve support for Kotlin library projects. (`T3752`) - Automatically process Kotlin Metadata when keeping the `kotlin.Metadata` annotation. (`T3116`) ### Improved - Improve app startup times when using `-addconfigurationdebugging`. (`T17153`) - Automatically process Kotlin Metadata when keeping the `kotlin.Metadata` annotation. (`T3116`) ### Bug fixes - Prevent merging classes with native methods that would result in UnsatisfiedLinkError. - Fix optimization of simple enums (optimization `class/unboxing/enums`). - Prevent potential build time `NullPointerException` when processing Kotlin interface methods. - Fix ProGuard Gradle Plugin not working correctly on Windows. (`PGD-272`) ## Version 7.2.2 ### Bug fixes - Fix "Can't save configuration file" error in ProGuardGUI. (`PGD-220`) - Fix rule configurations that extend annotation classes. (`PGD-229`) - Fix "No matching variant" Gradle plugin error when using Gradle 7.4 and Java 8. (`PGD-2311`) - Fix potential Kotlin metadata initialization issue when using the `-Xno-optimized-callable-references` compiler option. (`T16486`) - Fix missing warnings in ProGuardGUI. (`PGD-239`) ### Improved - Remove Kotlin Intrinsics strings by default, without requiring the `-keepkotlinmetadata` option. (`T16518`) ## Version 7.2.1 ### Improved - Remove `throwUninitializedProperty` parameter strings when using `-keepkotlinmetadata`. (`T4696`) ### Bug fixes - Fix possible `NullPointerException` in the `ConigurationLogger` when printing rules about constructors. ## Version 7.2.1 ### Java Support - Update maximum supported Java class version to 62.65535 (Java 18 ea). (T13973) - Deprecate `-target` for classes compiled with Java > 11. (`T13968`) ### Improved - Add [`consumerRuleFilter`](setup/gradleplugin.md#consumerrulefilter) to the ProGuard Gradle plugin. (`T4134`) ### Bug fixes - Prevent the generation of Windows reserved names. (`T3937`) - Prevent "Expecting type and name" parse error when using the `androidx.window` library in an Android project. (`T13715`) - Fix shrinking of annotations during GSON optimization. ## Version 7.2 ### Java Support New Java versions are released every 6 months. To allow ProGuard to continue to optimize, obfuscate and shrink Java class files ProGuard now supports all Java versions including Java 17. - Add support for Java 17. (`PGD-132`) ### Kotlin Support New Kotlin versions are released every 6 months. To allow ProGuard to continue to optimize, obfuscate and shrink Kotlin generated class files and their corresponding metadata ProGuard now supports Kotlin reading Kotlin classes from version 1.0 to 1.6 and writing Kotlin metadata with version 1.5 (readable by Kotlin reflection library / compiler 1.4 - 1.6). - Add support for processing Kotlin 1.5 and 1.6 metadata. (`PGD-179`, `DGD-3467`, `PGC-31`, `T4777`) - Add support for matching Kotlin inline class type parameters when using `includedescriptorclasses` keep rule modifier (requires `-keepkotlinmetadata`). (`T13653`) ### Improved - Upgrade log4j2 dependency to 2.17.1 in response to CVE-2021-44228, CVE-2021-45046, CVE-2021-45105 and CVE-2021-44832 - Improve build speed when using `-keepkotlinmetadata`. (`T5205`) ### Bug fixes - Fix potential `NullPointerException` when initializing Kotlin callable references. (`T5899`) - Prevent requiring `--enable-preview` on a JVM for Java 16 class files (write class file version `60.0` instead of `60.65535`). - Fix potential `StringIndexOutOfBoundsException` during signing. (`T7004`) - Fix potential `StackOverflowError` when processing Java 16 records with type annotations. (`PGD-182`) - Fix potential `StringOutOfBoundsException` when processing Kotlin callable references. (`T5927`) - Fix potential `NullPointerException` when processing Kotlin callable references. (`T6138`) - Fix potential `Stack size becomes negative` exception when processing large methods. (`T5721`) - Fix potential `ClassFormatError` due to adding multiple annotation attributes when processing Kotlin code. - Fix potential `NullPointerException` due to missing classes. - Prevent possible `LinkageError` when making package-private final methods that are shadowed protected. (`T7056`) ## Version 7.1.2 ### Bug fixes - Prevent possible R8 compilation error when using enum default values in an interface definition. - Fix enabling of optimization when `proguard-android-optimize.txt` is specified as the default in the Gradle plugin. ## Version 7.1.1 ### Miscellaneous #### Bug fixes - Fix initialization and obfuscation of Kotlin callable references when using Kotlin 1.4. (`T5631`) - Fail build when `IncompleteClassHierarchyException` is encountered. (`T5007`) - Fix potential hanging of ProGuard process during optimization or obfuscation. ## Version 7.1 (June 2021) ### AGP 7 compatible Gradle plugin The way ProGuard is integrated into Android projects is changing because AGP 7 will no longer allow developers to disable R8 with the `android.enableR8` Gradle property. ProGuard 7.1 includes a new Gradle plugin which allows for a seamless integration with this new AGP version. A detailed and step-by-step guide for transition from the previous plugin is provided in the [upgrading guide](setup/upgrading.md). - Simple integration with Android Gradle Projects by adding a dependency on the [ProGuard Gradle Plugin artifact](https://mvnrepository.com/artifact/com.guardsquare/proguard-gradle/7.1.0) and applying the `com.guardsquare.proguard` plugin, as presented in the [Gradle plugin setup instructions](setup/gradleplugin.md). ### Java support New Java versions are released every 6 months. To allow ProGuard to continue to optimize, obfuscate and shrink Java class files we have added support for the latest releases, up to Java 16. This includes - Add support for processing Java 14, 15 and 16 class files. (`PGC-0015`, `PGD-0064`) - Add support for Java 14 sealed classes. (`PGD-0064`) - Add support for records (previewed in Java 15/16, targeted for Java 17). (`PGD-0064`) ### New optimizations ProGuard 7.1 adds 5 new code [optimizations](configuration/optimizations.md). These optimization aim at reducing the number of classes required by the application and help pinpointing the specialized type wherever possible. This can have a positive impact on code shrinking and application performances. - `method/specialization/returntype` - specializes the types of method return values, whenever possible. - `method/specialization/parametertype` - specializes the types of method parameters, whenever possible. - `method/generalization/class` - generalizes the classes of method invocations, whenever possible. - `field/specialization/type` - specializes the types of fields, whenever possible. - `field/generalization/class` - generalizes the classes of field accesses, whenever possible. ### Maven central ProGuard was previously hosted at JCenter, but this repository has recently been removed. ProGuard is now published to [Maven Central](https://mvnrepository.com/artifact/com.guardsquare/proguard-gradle/7.1.0). - Simple integration : follow the [Gradle plugin setup instructions](setup/gradleplugin.md) to select the `mavenCentral()` repository and dependencies to the `proguard-gradle` artifact. ### Easier configuration The [`-addconfigurationdebugging`](configuration/usage.md#addconfigurationdebugging) allows to help configuring ProGuard `-keep` rules. For more details and to see this in action have a look at [Configuring ProGuard, an Easy Step-by-Step Tutorial](https://www.guardsquare.com/blog/configuring-proguard-an-easy-step-by-step-tutorial). `-addconfigurationdebugging` previously reported `-keep` rule suggestions for classes, methods or fields that did not exist in the app. Such `-keep` rules are not necessary. ProGuard 7.1 improves the suggestion of `-keep` rules by only suggesting rules for classes, methods and fields that were actually in the original application (`DGD-3264`). - More precise keep rule suggestions provided by `-addconfigurationdebugging`. - Easier ProGuard -keep rules configuration. - Reduced ProGuard configuration size. ### Miscellaneous #### Improvements - Remove Gradle plugin dependency on Android build tools. (`PGD-0066`) - Improve error message when missing classes result in an incomplete class hierarchy. (`DGD-0013`) - Improve speed of horizontal class merging. (`DGD-1471`) - Fix potential `NullPointerException` during GSON optimization. (`T3568`, `T3607`) - Fix potential dangling switch labels and next local index in `GsonDeserializationOptimizer`. - Fix potential `NoClassDefFoundError` at runtime after applying Gson optimization to library project. (`T2374`) - Improve general performance and stability of optimization. #### Bug fixes - Fix `ProGuardTask` Gradle task compatibility with Gradle 7. (`PGD-0136`) - Fix potentially incorrect class merging optimizations that could cause a run-time `NullPointerException`. (`DGD-3377`) - Fix Gradle task error when using an existing `-outjar` directory. (`PGD-0106`) - Fix potential class merging optimization issue resulting in `ArrayIndexOutOfBoundsException` during build. (`DGD-1995`) - Fix enum unboxing for already obfuscated code. (`DGD-0567`) - Fix potential parameter removal optimization issue when optimizing constructors. (`PGD-0018`, `PGD-0019`) - Fix potential `IllegalArgumentException` (Stack size becomes negative) in `class/merging/wrapper` optimization. (`DGD-2587`) - Fix wrapper class merging with `new`/`dup`/`astore` sequences. (`DGD-1564`) - Fix potential incorrect removal of exception handlers during optimization. (`DGD-3036`) - Fix potential, incorrect advanced code optimizations. (`DGD-3289`) - Disallow merging of nest hosts or members during class merging optimization. (`PGD-0037`) - Fix packaging of Ant plugin. (`PGD-0052`) - Fix potential IllegalArgumentException in GSON optimization. (`PGD-0047`) - Fix writing of kept directories. (`PGD-0110`) - Fix storage and alignment of uncompressed zip entries. (`DGD-2390`) - Fix processing of constant boolean arrays. (`DGD-2338`) - Prevent injected classes from being merged into other classes. ## Version 7.0 (Jun 2020) | Version| Issue | Module | Explanation |--------|----------|----------|---------------------------------- | 7.0.1 | DGD-2382 | CORE | Fixed processing of Kotlin 1.4 metadata annotations. | 7.0.1 | DGD-2494 | CORE | Fix naming conflict resolution potentially resulting in the renaming of library members. | 7.0.1 | | RETRACE | Extend expressions supported by Retrace. | 7.0.0 | | CORE | Added support for [Kotlin metadata](languages/kotlin.md), with new option `-keepkotlinmetadata`. | 7.0.0 | PGD-32 | CORE | Allowing Java 14 class files. | 7.0.0 | | CORE | Optimizing away instance references to constructor-less classes. | 7.0.0 | DGD-1489 | CORE | Fixed potential `IllegalArgumentException` with message `Value "x" is not a reference value` when optimizing code with conditional casts. | 7.0.0 | PGD-12 | CORE | Fixed building gradle plugin. ## Version 6.2 (Oct 2019) | Version| Issue | Module | Explanation |--------|----------|----------|---------------------------------- | 6.2.2 | | GRADLE | Fixed missing runtime dependencies. | 6.2.1 | PGD-12 | GRADLE | Fixed build of Gradle plugin. | 6.2.1 | DGD-827 | CORE | Fixed retracing of obfuscated class / method names in some cases. | 6.2.1 | PGD-15 | CORE | Fixed potential `ClassFormatError` due to corrupt LineNumberTables when processing class files generated by groovy 2.5.5+. | 6.2.1 | DGD-1503 | CORE | Added default filter to prevent processing of versioned class files. | 6.2.1 | DGD-1364 | DOCS | Documented the mapping file format. | 6.2.1 | DGD-950 | CORE | Fixed potential `EmptyStackException` when generating mapping files with inlined methods in rare cases. | 6.2.1 | PGD-10 | CORE | Fixed potential `VerifyError` when optimizing classes with class version `11+` due to nest based access. | 6.2.1 | PGD-11 | CORE | Fixed gradle example for processing libraries. | 6.2.1 | PGD-8 | CORE | Fixed potential `IllegalArgumentException` with message `Value "x" is not a reference value` when optimizing code with conditional casts. | 6.2.1 | DGD-1424 | CORE | Fixed incomplete fix in case of inlining method with type annotations. | 6.2.0 | DGD-1359 | CORE | Fixed removal of non-static write-only fields (optimization `field/removal/writeonly`). | 6.2.0 | DGD-1424 | CORE | Fixed potential build error when inlining methods into code attributes with type annotations. | 6.2.0 | PGD-764 | GUI | Fixed text fields for obfuscation dictionaries. | 6.2.0 | PGD-751 | GUI | Fixed boilerplate settings and foldable panels. | 6.2.0 | DGD-1418 | CORE | Fixed class member access checking for backporting of closures. | 6.2.0 | DGD-1359 | CORE | Fixed removal of write-only fields. | 6.2.0 | PGD-756 | CORE | Fixed detection of functional interfaces. | 6.2.0 | DGD-1317 | CORE | Fixed potential build errors when optimizing methods with many parameters. | 6.2.0 | PGD-753 | CORE | Fixed processing of signature attributes in constructors of inner classes and enum types. | 6.2.0 | PGD-759 | CORE | Fixed backporting of Java 8 API types when used as parameters in private methods. | 6.2.0 | PGD-1 | CORE | Fixed optimization of exception handling in Kotlin runtime. ## Version 6.1 (May 2019) - \[PGD-750\] Fixed UnsupportedOperationException when optimizing enum types in closure arguments. - \[DGD-1276\] Fixed optimization of Groovy code constructs causing Dalvik conversion errors. - \[DGD-1258\] Fixed potential VerifyError in JVM caused by inlining methods from super class. - \[PGD-752\] Fixed preverification of initializers with inlined exception throwing code. - \[PGD-755\] Fixed compatibility with older versions of ProGuard when used in combination with the Android gradle plugin. - Fixed potential NullPointerException when using keep rules with includecode modifier. - \[PGD-749\] Fixed merging of classes containing type annotations with empty targets. - \[PGD-748\] Fixed optimization of exceptions as unused parameters. - \[PGD-747\] Removed unwanted logging injection for native library loading. - \[PGD-745\] Fixed IllegalArgumentException for comparison of constant arrays with NaN float/double values. - \[PGD-744\] Fixed potential ClassCastException when optimizing method handles of simple enum types. - \[DGD-504\] Fixed potential build errors when optimizing Kotlin code that combines `let` and the Elvis operator `?:`. - \[PGD-741\] Improved incremental obfuscation. - \[DGD-1050\] Fixed obfuscation of enums in annotations, for conversion by dx or D8. - \[PGD-739\] Fixed the counter for the number of inlined constant parameters. - \[PGD-188\] Added support for Java 10, 11, and 12. - \[PGD-740\] Fixed shrinking of nest member attributes. - \[PGD-735\] Fixed processing of parameter annotations in constructors of inner classes and enum types. - \[PGD-734\] Fixed processing of lambda expressions for non-obvious functional interfaces. - Added optimization of code that uses the GSON library, removing reflection to improve size and performance of the processed code. - Added automatic backporting of code using the Java 8 stream API, enabled by adding net.sourceforge.streamsupport as a library. - Added automatic backporting of Java 8 time API, enabled by adding org.threeten as a library. - Added option `-assumevalues`, with corresponding optimizations on integer intervals. - \[PGD-731\] Fixed incorrect error message about generics in wildcard expressions. - \[PGD-730\] Fixed infinite loop in optimization. - \[PGD-720\] Fixed unboxed enum types being compared to null. - Fixed writing out mapping files with duplicate lines. - Fixed potentially inconsistent local variable type table. - Fixed backporting of default interface methods if an interface extends another one. - Now uniformly reading and writing text files with UTF-8 encoding. - \[PGD-708\] Fixed possible verification error due to exception handlers in Kotlin initializers. - \[PGD-712\] Fixed NullPointerException triggered by `module-info` classes with `requires` without version. - \[PGD-709\] Improved error messages for problems parsing wildcards. ## Version 6.0 (Feb 2018) - \[PGD-701\] Fixed potential VerifyError in the presence of branches to instruction offset 0. - Fixed backporting of lambda functions using the alternative factory method. - \[PGD-699\] Fixed obfuscation of closures that implement multiple interfaces. - \[PGD-694\] Fixed classes prefix when writing output to directories. - \[PGD-186\] Added support for Java 10. - \[PGD-698\] Fixed possible NullPointerException when parsing configuration. - \[PGD-655\] Fixed access from static methods to protected methods in superclasses. - \[PGD-693\] Fixed obfuscation of closures for functional interfaces with default methods. - Added new option `-if`, to specify conditions for [`-keep`](configuration/usage.md#keep) options. - Added new option `-addconfigurationdebugging`, to instrument the code to get feedback about missing configuration at runtime. - Added new option [`-android`](configuration/usage.md#android) to tune processing for Android. - Added support for references to matched wildcards in regular expressions in `-keep` options. - Added new options `-assumenoexternalsideeffects`, `-assumenoescapingparameters`, and `-assumenoexternalreturnvalues`, to express fine-grained assumptions for better code optimization. - Added backporting of Java 8 code. - Added backporting and support for Java 9 code. - Improved vertical class merging. - \[PGD-689\] Fixed optimization potentially causing unexpected error while processing Kotlin bytecode. - \[PGD-690\] Fixed NullPointerException when editing keep specifications in GUI. - \[PGD-688\] Fixed UnsupportedOperationException while optimizing type annotations. - \[PGD-654\] Fixed processing of MethodParameters attributes with nameless parameters. - \[PGD-662\] Fixed obfuscation causing clashing private and default method names. - \[PGD-684\] Fixed obfuscation of extensions of functional interfaces that are implemented with closures. - \[PGD-681\] Fixed potential IllegalArgumentException in simplification of tail recursion. - \[PGD-672\] Fixed memory leak writing compressed zip entries. - \[PGD-652\] Fixed possible NullPointerException due to missing variable initialization. - \[PGD-641\] Fixed possible NullPointerException due to optimized enum types. - \[PGD-630\] Fixed possible NullPointerException in optimization step. - \[PGD-637\] Fixed simplification of enum types in invokedynamic calls. - Fixed merging of classes sometimes resulting in final methods being overridden. - Fixed simplification of enum types that are stored in arrays. - Fixed VerifyError triggered by merging classes with shrinking disabled. ## Version 5.3 (Sep 2016) - Avoiding obfuscated name clashes with library classes. - Fixed processing of generic signatures with inner classes. - Fixed processing of generic signatures with array types as bounds. - Fixed processing of wide branch instructions. - Fixed shrinking of nameless parameters attribute. - Fixed optimization of code with unreachable exception handlers. - Fixed optimization of enum types with custom fields. - Fixed optimization of enum types with custom static methods. - Fixed optimization of lambda method types. - Fixed optimization of parameter annotations. - Fixed optimization of swap/pop constructs. - Fixed adapting class access for referenced class members. - Fixed adapting invocations for referenced class members. - Preserving class member signature classes if `includedescriptorclasses` is specified. - Allowing empty output jars if [`-ignorewarnings`](configuration/usage.md#ignorewarnings) is specified. - Avoiding exceptions when inlining invalid line number instruction offsets. - Fixed preverification of wildcard exceptions. - Fixed ReTrace for field names. - Fixed ReTrace for negative line numbers. - Improved ReTrace regular expression for Logback and Log4j. - Added Gradle build file. - Updated documentation and examples. ## Version 5.2 (Jan 2015) - Added encoding of optimized line numbers in ProGuard. - Added decoding of optimized stack frames in ReTrace. - Added overflow checks when writing out class files. - Fixed shrinking of default methods in subinterfaces. - Fixed optimization of closures with signatures containing merged classes. - Fixed conservative optimization of instructions that may throw exceptions. - Fixed internal processing of primitive array types. - Updated documentation and examples. ## Version 5.1 (Oct 2014) - Fixed processing of various kinds of closures in Java 8. - Fixed shrinking of generic signatures in classes and methods. - Fixed shrinking of debug information about generic local variable types. - Fixed optimization of default implementations in interfaces. - Fixed optimization of variable initializations. - Fixed obfuscation of internal class names in strings. - Updated documentation and examples. ## Version 5.0 (Aug 2014) - Added support for Java 8. - Added [`-keep`](configuration/usage.md#keep) modifier `includedescriptorclasses`. - Added automatic suggestions for keeping attributes. - Clearing preverification information when `-dontpreverify` is specified. - Extended optimization support for conservative optimization with java system property `optimize.conservatively`. - Fixed occasional preverification problem. - Fixed shrinking of generic class signatures. - Fixed shrinking of generic variable signatures. - Fixed analysis of unused parameters for bootstrap methods in library classes. - Fixed inlining problem of non-returning subroutines. - Fixed possible IllegalArgumentException and ArrayIndexOutOfBoundsException in enum simplification. - Fixed unnecessary notes about dynamic class instantiations with constant class names. - Fixed preverification of unnecessary casts of null values. - Fixed lazy resolution of output jars in Gradle task. - Fixed processing of synthetic code with alternative initializer invocations. - Improved handling of symbolic links in shell scripts. - Improved default path in Windows bat files. - Updated documentation and examples. ## Version 4.11 (Dec 2013) - Added simplification of basic enum types. - Added reading and writing of apk and aar archives. - Fixed criteria for class merging. - Fixed simplification of variable initializations. - Fixed simplification of redundant boolean variables. - Fixed optimization of unused stack entries in exception handlers. - Fixed correction of access flags after class merging, method inlining, and class repackaging. - Refined criterion for method inlining. - Updated documentation and examples. ## Version 4.10 (Jul 2013) - Made Gradle task resolve files lazily. - Enabled as-needed execution in Gradle task. - Using standard string interpolation for Gradle configuration. - Reduced log levels for console output in Gradle task. - Updated documentation and examples. ## Version 4.9 (Mar 2013) - Added Gradle task. - Added more peephole optimizations for strings. - Improved optimization of classes with static initializers. - Improved processing of finally blocks compiled with JDK 1.4 or older. - Fixed shrinking of access widening abstract methods, for the Dalvik VM. - Fixed overly aggressive shrinking of class annotations. - Fixed processing of unused classes in generic signatures. - Fixed merging of classes with similar class members. - Added java system property `optimize.conservatively` to allow for instructions intentionally throwing `NullPointerException`, `ArrayIndexOutOfBoundsException`, or `ClassCastException` without other useful effects. - Fixed optimization of unnecessary variable initializations. - Fixed optimization of code involving NaN. - Fixed inlining of methods that are supposed to be kept. - Fixed preverification of artificially convoluted dup constructs. - Fixed quotes for java commands in .bat scripts. - Improved handling of non-sequential line number information. - Now requiring Java 5 or higher for running ProGuard. - Updated build files. - Updated documentation and examples. ## Version 4.8 (May 2012) - Added more peephole optimizations for strings. - Added support for multiple external configuration files in Ant configurations. - Added support for Ant properties in external configuration files. - Fixed parsing of empty file filters on input and output. - Fixed parsing of '\*' wildcard for file filters and name filters. - Fixed obfuscation of private methods that are overridden in concrete classes with intermediary abstract classes and interfaces (workaround for Oracle bugs \#6691741 and \#6684387). - Fixed optimization of complex finally blocks, compiled with JDK 1.4 or earlier. - Fixed optimizing signatures of methods that are marked as not having side effects. - Fixed optimization of long local variables possibly causing verification error for register pairs. - Fixed merging of classes defined inside methods. - Fixed stack consistency in optimization step. - No longer removing debug information about unused parameters, for `-keepparameternames` or `-keepattributes`. - Fixed updating manifest files with carriage return characters. - Now removing unreachable code in preverification step. - Improved default regular expression for stack traces in ReTrace. - Updated documentation and examples. ## Version 4.7 (Dec 2011) - Added support for Java 7. - Parsing unquoted file names with special characters more leniently. - Added support for instance methods overriding class methods. - Added removal of unused parameterless constructors. - Added removal of empty class initializers. - Added peephole optimizations for constant strings. - Avoiding idle optimization passes. - Improved removal of unused constants after obfuscation. - Fixed removal of unused classes referenced by annotations. - Fixed simplifying parameters of constructors that should actually be preserved. - Fixed simplifying parameters of large numbers of similar constructors. - Fixed exceptions in optimization of unusual obfuscated code. - Fixed NullPointerException when specifying `-keepclassmembers` without specific class or class members. - Fixed potential problems with mixed-case class name dictionaries when not allowing mixed-case class names. - Fixed obfuscation of classes with EnclosingMethod attributes that don't specify methods. - Fixed preverification of returning try blocks with finally blocks, inside try blocks, when compiled with JDK 1.4. - Fixed sorting of interfaces containing generics. - Fixed paths in shell scripts. - Fixed filling in of text fields showing class obfuscation dictionary and package obfuscation dictionary from configuration in GUI. - Worked around Oracle Java 6/7 bug \#7027598 that locked the GUI on Linux. - Updated documentation and examples. ## Version 4.6 (Feb 2011) - Added support for synthetic, bridge, and varargs modifiers in configuration. - Added detection of atomic updater construction with constant arguments. - Fixed merging of package visible classes. - Fixed optimization of fields that are only accessed by reflection. - Fixed optimization of read-only or write-only fields that are volatile. - Fixed handling of side-effects due to static initializers. - Fixed handling of bridge flags in obfuscation step. - Fixed handling of super flag when merging classes. - Fixed updating of variable tables when optimizing variables. - Fixed removal of unused parameters with 32 or more parameters. - Fixed incorrect removal of exception handler for instanceof instruction. - Fixed inlining of methods with unusual exception handlers. - Fixed optimization of unusual code causing stack underflow. - Fixed keeping of constructor parameter names. - Fixed unwanted wrapping of non-standard META-INF files. - Fixed filtering of warnings about references to array types. - Fixed overriding of warning option and note option in Ant task. - Improved detection of file name extensions for canonical paths. - Improved printing of seeds specified by [`-keep`](configuration/usage.md#keep) options. - Improved printing of notes about unkept classes. - Improved checking whether output is up to date. - Updated documentation and examples. ## Version 4.5 (Jun 2010) - Added option `-keepparameternames`. - [`-dontskipnonpubliclibraryclasses`](configuration/usage.md#dontskipnonpubliclibraryclasses) is now set by default. Added `-skipnonpubliclibraryclasses` as an option. - Made processing independent of order of input classes to get even more deterministic output. - Improved constant field propagation. - Improved renaming of resource files in subdirectories of packages. - Avoiding making fields in interfaces private. - Optimizing exception handlers for monitorexit instruction. - Reduced maximum allowed code length after inlining from 8000 bytes to 7000 bytes. - Fixed missing warnings about missing library classes. - Fixed shrinking of annotations with arrays of length 0. - Fixed handling of -0.0 and NaN values when simplifying expressions. - Fixed copying of exception handlers when simplifying tail recursion calls. - Fixed optimization of introspected fields. - Fixed simplification of unnecessary variable initializations. - Fixed evaluation of subroutines in pre-JDK 1.5 code. - Fixed updating of access flags in inner classes information. - Fixed disabling of field privatization. - Fixed invocations of privatized methods. - Fixed updating of local variable debug information in optimization step. - Fixed print settings without file name in GUI. - Fixed field privatization setting in GUI. - Fixed saving incorrectly quoted arguments in GUI. - Fixed handling of regular expressions with only negators. - Fixed unwanted wrapping of non-standard META-INF files. - Fixed regular expression pattern for constructors in ReTrace. - Updated documentation and examples. ## Version 4.4 (Jul 2009) - Added new peephole optimizations. - Added option [`-optimizations`](configuration/usage.md#optimizations) for fine-grained configuration of optimizations. - Added option [`-adaptclassstrings`](configuration/usage.md#adaptclassstrings) for adapting string constants that correspond to obfuscated classes. - Added option [`-keeppackagenames`](configuration/usage.md#keeppackagenames) for keeping specified package names from being obfuscated. - Added option [`-keepdirectories`](configuration/usage.md#keepdirectories) for keeping specified directory entries in output jars. - Extended options [`-dontnote`](configuration/usage.md#dontnote) and [`-dontwarn`](configuration/usage.md#dontwarn) for fine-grained configuration of notes and warnings. - Added option `-regex` in ReTrace, for specifying alternative regular expressions to parse stack traces. - Extended renaming of resource files based on obfuscation. - Improved inlining of constant parameters and removal of unused parameters. - Avoiding bug in IBM's JVM for JSE, in optimization step. - Avoiding ArrayIndexOutOfBoundsException in optimization step. - Fixed configuration with annotations that are not preserved themselves. - Fixed preverification of invocations of super constructors with arguments containing ternary operators. - Fixed processing of unreachable exception handlers. - Fixed merging of exception classes. - Fixed repeated method inlining. - Fixed inlining of finally blocks surrounded by large try blocks, compiled with JDK 1.4 or earlier. - Fixed optimization of complex finally blocks, compiled with JDK 1.4 or earlier. - Fixed obfuscation of anonymous class names, if `EnclosingMethod` attributes are being kept. - Fixed obfuscation of inner class names in generic types. - Fixed decoding of UTF-8 strings containing special characters. - Fixed copying of debug information and annotations when merging classes. - Fixed writing out of unknown attributes. - Fixed updating manifest files with split lines. - Updated documentation and examples. ## Version 4.3 (Dec 2008) - Added class merging. - Added static single assignment analysis. - Added support for annotation and enumeration class types in configuration. - Refined shrinking of fields in case of unusual `-keepclassmembers` options. - Added simplification of tail recursion calls. - Added new peephole optimizations. - Fixed optimization of unused variable initializations causing negative stack sizes. - Fixed optimization of unusual initialization code causing NullPointerExceptions. - Fixed optimization of half-used long and double parameters. - Fixed processing of complex generics signatures. - Working around suspected java compiler bug with parameter annotations on constructors of non-static inner classes. - Fixed obfuscation of classes with inner classes whose names are preserved. - Fixed access of protected methods in repackaged classes. - Added options [`-classobfuscationdictionary`](configuration/usage.md#classobfuscationdictionary) and `-packageobfuscationdictionary`. - Adapting more types of resource file names based on obfuscation. - Extended warnings about incorrect dependencies. - Added start-up scripts and build scripts. - Updated documentation and examples. ## Version 4.2 (Mar 2008) - Refined data flow analysis in optimization step. - Fixed handling of exceptions when inlining subroutines. - Fixed inlining of incompatible code constructs between different java versions. - Fixed computation of local variable frame size. - Fixed optimization of infinite loops. - Fixed optimization of subroutine invocations. - Fixed optimization of floating point remainder computations. - Fixed removal of unused parameters in method descriptors containing arrays of longs or doubles. - Added undocumented java system properties `maximum.inlined.code.length` (default is 8) and `maximum.resulting.code.length` (defaults are 8000 for JSE and 2000 for JME), for expert users who read release notes. - Fixed processing of generic types in Signature attributes in shrinking and optimization steps. - Fixed processing of inner class names in Signature attributes in obfuscation step. - Improved adapting resource file names following obfuscated class names. - Fixed interpretation of package names in GUI. - Fixed default settings for Xlets in GUI. - Updated documentation and examples. ## Version 4.1 (Dec 2007) - Fixed shrinking of default annotation element values. - Fixed optimization of invocations of methods in same class that are accessed through extensions. - Fixed optimization of invocations of synchronized methods without other side-effects. - Fixed optimization of some non-returning subroutines. - Fixed handling of local variable debug information when inlining methods. - Avoiding StackOverflowErrors during optimization of complex methods. - Fixed obfuscation of potentially ambiguous non-primitive constants in interfaces. - Fixed preverification of some code constructs involving String, Class, and exception types. - The Ant task now allows empty `` and `` elements. - Updated documentation and examples. ## Version 4.0 (Sep 2007) - Added preverifier for Java 6 and Java Micro Edition, with new options `-microedition` and `-dontpreverify`. - Added new option [`-target`](configuration/usage.md#target) to modify java version of processed class files. - Made [`-keep`](configuration/usage.md#keep) options more orthogonal and flexible, with option modifiers `allowshrinking`, `allowoptimization`, and `allowobfuscation`. - Added new wildcards for class member descriptors: "`***`", matching any type, and "`...`", matching any number of arguments. - Added support for configuration by means of annotations. - Improved shrinking of unused annotations. - Added check on modification times of input and output, to avoid unnecessary processing, with new option `-forceprocessing`. - Added new options [`-flattenpackagehierarchy`](configuration/usage.md#flattenpackagehierarchy) and `-repackageclasses` (replacing `-defaultpackage`) to control obfuscation of package names. - Added new options [`-adaptresourcefilenames`](configuration/usage.md#adaptresourcefilenames) and `-adaptresourcefilecontents`, with file filters, to update resource files corresponding to obfuscated class names. - Added detection of dynamically accessed fields and methods. - Now treating `Exceptions` attributes as optional. - Now respecting naming rule for nested class names (`EnclosingClass$InnerClass`) in obfuscation step, if `InnerClasses` attributes or `EnclosingMethod` attributes are being kept. - Added new inter-procedural optimizations: method inlining and propagation of constant fields, constant arguments, and constant return values. - Added optimized local variable allocation. - Added more than 250 new peephole optimizations. - Improved making classes and class members public or protected. - Now printing notes on suspiciously unkept classes in parameters of specified methods. - Now printing notes for class names that don't seem to be fully qualified. - Added support for uppercase filename extensions. - Added tool tips to the GUI. - Rewritten class file I/O code. - Updated documentation and examples. Upgrade considerations: - Since ProGuard now treats the `Exceptions` attribute as optional, you may have to specify `-keepattributes Exceptions`, notably when processing code that is to be used as a library. - ProGuard now preverifies code for Java Micro Edition, if you specify the option `-microedition`. You then no longer need to process the code with an external preverifier. - You should preferably specify [`-repackageclasses`](configuration/usage.md#repackageclasses) instead of the old option name `-defaultpackage`. ## Version 3.11 (Dec 2007) - Fixed optimization of invocations of methods in same class that are accessed through extensions. - Fixed optimization of invocations of synchronized methods without other side-effects. - Updated documentation and examples. ## Version 3.10 (Aug 2007) - Now handling mixed-case input class names when `-dontusemixedcaseclassnames` is specified. - Fixed optimization of synchronization on classes, as compiled by Eclipse and Jikes. - Fixed optimization of switch statements with unreachable cases. - Avoiding merging subsequent identically named files. - Updated documentation and examples. ## Version 3.9 (Jun 2007) - Fixed processing of .class constructs in Java 6. - Fixed repeated processing of .class constructs. - Fixed possible division by 0 in optimization step. - Fixed handling of variable instructions with variable indices larger than 255. - Updated documentation and examples. ## Version 3.8 (Mar 2007) - Fixed optimization of parameters used as local variables. - Fixed obfuscation with conflicting class member names. - Fixed incremental obfuscation with incomplete mapping file for library jars. - Updated documentation and examples. ## Version 3.7 (Dec 2006) - Now accepting Java 6 class files. - Fixed shrinking of partially used annotations. - Improved incremental obfuscation, with new option `-useuniqueclassmembernames`. - Printing more information in case of conflicting configuration and input. - Fixed optimization of repeated array length instruction. - Fixed optimization of subsequent try/catch/finally blocks with return statements. - Fixed optimization of complex stack operations. - Fixed optimization of simple infinite loops. - Fixed optimization of expressions with constant doubles. - Tuned optimization to improve size reduction after preverification. - Fixed overflows of offsets in long code blocks. - Now allowing class names containing dashes. - Updated documentation and examples. ## Version 3.6 (May 2006) - No longer automatically keeping classes in parameters of specified methods from obfuscation and optimization (introduced in version 3.4). - Fixed inlining of interfaces that are used in .class constructs. - Fixed removal of busy-waiting loops reading volatile fields. - Fixed optimization of comparisons of known integers. - Fixed optimization of known branches. - Fixed optimization of method calls on arrays of interfaces. - Fixed optimization of method calls without side-effects. - Fixed optimization of nested try/catch/finally blocks with return statements. - Fixed initialization of library classes that only appear in descriptors. - Fixed matching of primitive type wildcards in configuration. - Fixed the boilerplate specification for enumerations in the GUI. - Updated documentation and examples. ## Version 3.5 (Jan 2006) - Fixed obfuscation of class members with complex visibility. - Fixed optimization bugs causing stack verification errors. - Fixed optimization bug causing overridden methods to be finalized. - Fixed optimization bug causing abstract method errors for retro-fitted library methods. - Fixed optimization bug evaluating code with constant long values. - Fixed bug in updating of optional local variable table attributes and local variable type table attributes after optimization. - Fixed interpretation of comma-separated class names without wildcards. - Updated documentation and examples. ## Version 3.4 (Oct 2005) - Extended optimizations: removing duplicate code within methods. - Extended regular expressions for class names to comma-separated lists. - Now automatically keeping classes in descriptors of kept class members. - Added verbose statistics for optimizations. - Added boilerplate Number optimizations in GUI. - Fixed `Class.forName` detection. - Fixed incremental obfuscation bug. - Fixed optimization bug causing stack verification errors. - Fixed optimization bugs related to removal of unused parameters. - Fixed exception when optimizing code with many local variables. - Fixed exception when saving configuration with initializers in GUI. - Updated documentation and examples. ## Version 3.3 (Jun 2005) - Extended optimizations: making methods private and static when possible, making classes static when possible, removing unused parameters. - Made file names relative to the configuration files in which they are specified. Added `-basedirectory` option. - Added [`-whyareyoukeeping`](configuration/usage.md#whyareyoukeeping) option to get details on why given classes and class members are being kept. - Added warnings for misplaced class files. - Improved printing of notes for `Class.forName` constructs. - Implemented '`assumenosideeffects`' nested element in Ant task. - Improved processing of annotations. - Fixed reading and writing of parameter annotations. - Fixed various optimization bugs. - Fixed wildcards not matching '-' character. - Fixed wildcard bug and checkbox bugs in GUI. - Setting file chooser defaults in GUI. - Leaving room for growBox in GUI on Mac OS X. - Properly closing configuration files. - Updated documentation and examples. ## Version 3.2 (Dec 2004) - Fixed JDK5.0 processing bugs. - Fixed optimization bugs. - Fixed relative paths in Ant task. - Improved speed of shrinking step. - Updated documentation and examples. ## Version 3.1 (Nov 2004) - Improved obfuscation and shrinking of private class members. - Added inlining of interfaces with single implementations. - Added option to specify obfuscation dictionary. - Added option to read package visible library class members. - Extended support for JDK5.0 attributes. - Fixed various optimization bugs. - Modified Ant task to accept paths instead of filesets. - Fixed two Ant task bugs. - Updated documentation and examples. ## Version 3.0 (Aug 2004) - Added bytecode optimization step, between shrinking step and obfuscation step. - Generalized filtered recursive reading and writing of jars, wars, ears, zips, and directories. - Added support for grouping input and output jars, wars, ears, zips, and directories. - Added support for applying mapping files to library classes. - Removed `-resourcejars` option. Resources should now be read using regular `-injars` options, using filters if necessary. - Rewrote Ant task. Input and output modification dates are not checked at the moment. Minor changes in XML schema: - Filters now specified using attributes. - '`outjars`' now nested element instead of attribute. - '`type`' attribute of `` element no longer defaults to '`void`'. - `<` and `>` characters now have to be encoded in embedded configurations. - `` task no longer accepts attributes. - Updated J2ME WTK plugin, now customizable through configuration file. - Updated GUI. - Fixed various processing bugs. - Fixed ReTrace parsing bugs. - Improved jar compression. - Updated documentation and examples. ## Version 2.1 (Mar 2004) - Added support for JDK1.5 classes. - Added additional wildcard for matching primitive types. - Added possibility to switch off notes about duplicate class definitions. - Fixed use of multiple filters on output jars. - Fixed option to keep all attributes. - Fixed various Ant task bugs. - Updated documentation and examples. ## Version 2.0 (Dec 2003) - Added a graphical user interface for ProGuard and ReTrace. - Added [`-applymapping`](configuration/usage.md#applymapping) option for incremental obfuscation. - Added support for filtering input and output files. - Added support for the J++ `SourceDir` attribute. - Improved detection of `.class` constructs. - Improved handling of misplaced manifest files. - Improved implementation of ReTrace. - Worked around String UTF-8 encoding bug affecting foreign characters. - Fixed exception when ignoring warnings. - Fixed various Ant task bugs. - Updated documentation and examples. ## Version 1.7 (Aug 2003) - Fixed various Ant task bugs. - Fixed ClassCastException due to explicitly used abstract classes with implicitly used interfaces targeted at JRE1.2 (the default in JDK1.4). - Fixed `-defaultpackage` bug for protected classes and class members. - Fixed ReTrace bug when retracing without line number tables. - Worked around zip package problems with duplicate out entries and rogue manifest files. - Added work-around for handling malformed legacy interface class files. - Updated documentation and examples. ## Version 1.6 (May 2003) - Added support for Ant. - Added support for the J2ME Wireless Toolkit. - Added support for reading and writing directory hierarchies. - Added option for specifying resource jars and directories. - Added support for wildcards in class member specifications. - Improved handling of the `-defaultpackage` option. - Improved stack trace parsing in ReTrace tool. - Fixed processing of libraries containing public as well as non-public extensions of non-public classes. - Fixed examples for processing libraries, midlets, and serializable code. - Updated documentation and examples. ## Version 1.5 (Jan 2003) - Fixed processing of retrofitted library interfaces. - Fixed processing of `.class` constructs in internal classes targeted at JRE1.2 (the default in JDK1.4). - Fixed [`-dump`](configuration/usage.md#dump) option when `-outjar` option is not present. - Updated documentation and examples. ## Version 1.4 (Nov 2002) - Now copying resource files over from the input jars to the output jar. - Added option to obfuscate using lower-case class names only. - Added better option for obfuscating native methods. - Added option not to ignore non-public library classes. - Added automatic `.class` detection for classes compiled with Jikes. - Updated documentation and examples. ## Version 1.3 (Sep 2002) - Added support for wildcards in class names. - Added tool to de-obfuscate stack traces. - Added options to print processing information to files. - Added option to rename source file attributes. - Fixed processing of implicitly used interfaces targeted at JRE1.2 (the default in JDK1.4) - Fixed processing of configurations with negated access modifiers. - Fixed duplicate class entry bug. - Updated documentation and examples. ## Version 1.2 (Aug 2002) - Improved speed. - Fixed processing of classes targeted at JRE1.2 (the default in JDK1.4) with references to their own subclasses. - Fixed processing of static initializers in J2ME MIDP applications. - Fixed processing of retrofitted interfaces (again). - Added more flexible handling of white space in configuration. - Updated documentation. ## Version 1.1 (Jul 2002) - Added automatic detection of `Class.forName("MyClass")`, `MyClass.class`, and `(MyClass)Class.forName(variable).newInstance()` constructs. This greatly simplifies configuration. - Added options to keep class names and class member names without affecting any shrinking. They are mostly useful for native methods and serializable classes. - Fixed processing of retrofitted interfaces. - Added handling of missing/invalid manifest file in input jar. - Updated documentation and examples. ## Version 1.0 (Jun 2002) - First public release, based on class parsing code from Mark Welsh's **RetroGuard**. ================================================ FILE: docs/md/manual/setup/ant.md ================================================ **ProGuard** can be run as a task in the Java-based build tool Ant (version 1.8 or higher). Before you can use the **`proguard`** task, you have to tell Ant about this new task. The easiest way is to add the following line to your `build.xml` file: Please make sure the class path is set correctly for your system. There are three ways to configure the ProGuard task: 1. using an external configuration file, 2. using embedded ProGuard configuration options, or 3. using the equivalent XML configuration tags. These three ways can be combined, depending on practical circumstances and personal preference. ## 1. An external ProGuard configuration file The simplest way to use the ProGuard task in an Ant build file is to keep your ProGuard configuration file, and include it from Ant. You can include your ProGuard configuration file by setting the [**`configuration`**](#configuration_attribute) attribute of your `proguard` task. Your ant build file will then look like this: ```xml ``` This is a convenient option if you prefer ProGuard's configuration style over XML, if you want to keep your build file small, or if you have to share your configuration with developers who don't use Ant. ## 2. Embedded ProGuard configuration options Instead of keeping an external ProGuard configuration file, you can also copy the contents of the file into the nested text of the **`proguard`** task (the PCDATA area). Your Ant build file will then look like this: -injars in.jar -outjars out.jar -libraryjars ${java.home}/lib/rt.jar -keepclasseswithmembers public class * { public static void main(java.lang.String[]); } Some minor syntactical changes are required in order to conform with the XML standard. Firstly, the **`#`** character cannot be used for comments in an XML file. Comments must be enclosed by an opening **``. All occurrences of the **`#`** character can be removed. Secondly, the use of **`<`** and `>` characters would upset the structure of the XML build file. Environment variables can be specified with the usual Ant style **`${...}`**, instead of the ProGuard style `<...>`. Other occurrences of **`<`** and `>` have to be encoded as `<` and `>` respectively. ## 3. XML configuration tags If you really prefer a full-blown XML configuration, you can replace the ProGuard configuration options by XML configuration tags. The resulting configuration will be equivalent, but much more verbose and difficult to read, as XML goes. The remainder of this page presents the supported tags. For a more extensive discussion of their meaning, please consult the traditional [Usage](../configuration/usage.md) section. You can find some sample configuration files in the **`examples/ant`** directory of the ProGuard distribution. ### Task Attributes and Nested Elements {: #attributes} The **``** task and the `` task can have the following attributes (only for **``**) and nested elements: `configuration`{: #configuration} = "*filename*" : Read and merge options from the given ProGuard-style configuration file. Note: for reading multiple configuration files or XML-style configurations, use the [**`configuration`**](#configuration_element) *element*. [**`skipnonpubliclibraryclasses`**](../configuration/usage.md#skipnonpubliclibraryclasses) = "*boolean*" (default = false) : Ignore non-public library classes. [**`skipnonpubliclibraryclassmembers`**](../configuration/usage.md#dontskipnonpubliclibraryclassmembers) = "*boolean*" (default = true) : Ignore package visible library class members. [**`target`**](../configuration/usage.md#target) = "*version*" (default = none) : Set the given version number in the processed classes. [**`forceprocessing`**](../configuration/usage.md#forceprocessing) = "*boolean*" (default = false) : Process the input, even if the output seems up to date. [**`printseeds`**](../configuration/usage.md#printseeds) = "*boolean or filename*" (default = false) : List classes and class members matched by the various **`keep`** commands, to the standard output or to the given file. [**`shrink`**](../configuration/usage.md#dontshrink) = "*boolean*" (default = true) : Shrink the input class files. [**`printusage`**](../configuration/usage.md#printusage) = "*boolean or filename*" (default = false) : List dead code of the input class files, to the standard output or to the given file. [**`optimize`**](../configuration/usage.md#dontoptimize) = "*boolean*" (default = true) : Optimize the input class files. [**`optimizationpasses`**](../configuration/usage.md#optimizationpasses) = "*n*" (default = 1) : The number of optimization passes to be performed. [**`allowaccessmodification`**](../configuration/usage.md#allowaccessmodification) = "*boolean*" (default = false) : Allow the access modifiers of classes and class members to be modified, while optimizing. [**`mergeinterfacesaggressively`**](../configuration/usage.md#mergeinterfacesaggressively) = "*boolean*" (default = false) : Allow any interfaces to be merged, while optimizing. [**`obfuscate`**](../configuration/usage.md#dontobfuscate) = "*boolean*" (default = true) : Obfuscate the input class files. [**`printmapping`**](../configuration/usage.md#printmapping) = "*boolean or filename*" (default = false) : Print the mapping from old names to new names for classes and class members that have been renamed, to the standard output or to the given file. [**`applymapping`**](../configuration/usage.md#applymapping) = "*filename*" (default = none) : Reuse the given mapping, for incremental obfuscation. [**`obfuscationdictionary`**](../configuration/usage.md#obfuscationdictionary) = "*filename*" (default = none) : Use the words in the given text file as obfuscated field names and method names. [**`classobfuscationdictionary`**](../configuration/usage.md#classobfuscationdictionary) = "*filename*" (default = none) : Use the words in the given text file as obfuscated class names. [**`packageobfuscationdictionary`**](../configuration/usage.md#packageobfuscationdictionary) = "*filename*" (default = none) : Use the words in the given text file as obfuscated package names. [**`overloadaggressively`**](../configuration/usage.md#overloadaggressively) = "*boolean*" (default = false) : Apply aggressive overloading while obfuscating. [**`useuniqueclassmembernames`**](../configuration/usage.md#useuniqueclassmembernames) = "*boolean*" (default = false) : Ensure uniform obfuscated class member names for subsequent incremental obfuscation. [**`usemixedcaseclassnames`**](../configuration/usage.md#dontusemixedcaseclassnames) = "*boolean*" (default = true) : Generate mixed-case class names while obfuscating. [**`flattenpackagehierarchy`**](../configuration/usage.md#flattenpackagehierarchy) = "*package\_name*" (default = none) : Repackage all packages that are renamed into the single given parent package. [**`repackageclasses`**](../configuration/usage.md#repackageclasses) = "*package\_name*" (default = none) : Repackage all class files that are renamed into the single given package. [**`keepparameternames`**](../configuration/usage.md#keepparameternames) = "*boolean*" (default = false) : Keep the parameter names and types of methods that are kept. [**`renamesourcefileattribute`**](../configuration/usage.md#renamesourcefileattribute) = "*string*" (default = none) : Put the given constant string in the **`SourceFile`** attributes. [**`preverify`**](../configuration/usage.md#dontpreverify) = "*boolean*" (default = true) : Preverify the processed class files if they are targeted at Java Micro Edition or at Java 6 or higher. [**`microedition`**](../configuration/usage.md#microedition) = "*boolean*" (default = false) : Target the processed class files at Java Micro Edition. [**`android`**](../configuration/usage.md#android) = "*boolean*" (default = false) : Target the processed class files at Android. [**`verbose`**](../configuration/usage.md#verbose) = "*boolean*" (default = false) : Write out some more information during processing. [**`note`**](../configuration/usage.md#dontnote) = "*boolean*" (default = true) : Print notes about potential mistakes or omissions in the configuration. Use the nested element [dontnote](#dontnote) for more fine-grained control. [**`warn`**](../configuration/usage.md#dontwarn) = "*boolean*" (default = true) : Print warnings about unresolved references. Use the nested element [dontwarn](#dontwarn) for more fine-grained control. *Only use this option if you know what you're doing!* [**`ignorewarnings`**](../configuration/usage.md#ignorewarnings) = "*boolean*" (default = false) : Print warnings about unresolved references, but continue processing anyhow. *Only use this option if you know what you're doing!* [**`printconfiguration`**](../configuration/usage.md#printconfiguration) = "*boolean or filename*" (default = false) : Write out the entire configuration in traditional ProGuard style, to the standard output or to the given file. Useful to replace unreadable XML configurations. [**`dump`**](../configuration/usage.md#dump) = "*boolean or filename*" (default = false) : Write out the internal structure of the processed class files, to the standard output or to the given file. [**`addconfigurationdebugging`**](../configuration/usage.md#addconfigurationdebugging) = "*boolean*" (default = false) : Adds debugging information to the code, to print out ProGuard configuration suggestions at runtime. *Do not use this option in release versions.* [**`` : Specifies the program jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). [**`` : Specifies the names of the output jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). [**`` : Specifies the library jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). [**``
[`` : Keep the specified directories in the output jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Preserve the specified classes *and* class members. [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Preserve the specified class members, if their classes are preserved as well. [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Preserve the specified classes *and* class members, if all of the specified class members are present. [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Preserve the names of the specified classes *and* class members (if they aren't removed in the shrinking step). [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Preserve the names of the specified class members (if they aren't removed in the shrinking step). [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Preserve the names of the specified classes *and* class members, if all of the specified class members are present (after the shrinking step). [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Print details on why the given classes and class members are being kept in the shrinking step. [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Assume that the specified methods don't have any side effects, while optimizing. *Only use this option if you know what you're doing!* [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Assume that the specified methods don't have any external side effects, while optimizing. *Only use this option if you know what you're doing!* [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Assume that the specified methods don't let any reference parameters escape to the heap, while optimizing. *Only use this option if you know what you're doing!* [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Assume that the specified methods don't return any external reference values, while optimizing. *Only use this option if you know what you're doing!* [**`` [*class\_member\_specifications*](#classmemberspecification) `` : Assume fixed values or ranges of values for primitive fields and methods, while optimizing. *Only use this option if you know what you're doing!* [**``
[`` : Perform only the specified optimizations. [**``
[`` : Keep the specified package names from being obfuscated. If no name is given, all package names are preserved. [**``
[`` : Preserve the specified optional Java bytecode attributes, with optional wildcards. If no name is given, all attributes are preserved. [**`` : Adapt string constants in the specified classes, based on the obfuscated names of any corresponding classes. [**`` : Rename the specified resource files, based on the obfuscated names of the corresponding class files. [**`` : Update the contents of the specified resource files, based on the obfuscated names of the processed classes. [**`` : Don't print notes about classes matching the specified class name filter. [**`` : Don't print warnings about classes matching the specified class name filter. *Only use this option if you know what you're doing!* ``
`` : The first form includes the XML-style configuration specified in a **``** task (or `` task) with attribute **`id`** = "*ref\_id*". Only the nested elements of this configuration are considered, not the attributes. The second form includes the ProGuard-style configuration from the specified file. The element is actually a **`fileset`** element and supports all of its attributes and nested elements, including multiple files. ## Class Path Attributes and Nested Elements {: #classpath} The jar elements are **`path`** elements, so they can have any of the standard **`path`** attributes and nested elements. The most common attributes are: `path` = "*path*" : The names of the jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories), separated by the path separator. `location` = "*name*" (or `file` = "*name*", or `dir` = "*name*", or `name` = "*name*") : Alternatively, the name of a single jar (or apk, aab, aar, war, ear, jmod, zip, or directory). `refid` = "*ref\_id*" : Alternatively, a reference to the path or file set with the attribute **`id`** = "*ref\_id*". In addition, the jar elements can have ProGuard-style filter attributes: `filter` = "[*file\_filter*](../configuration/usage.md#filefilters)" : An optional filter for all class file names and resource file names that are encountered. `apkfilter` = "[*file\_filter*](../configuration/usage.md#filefilters)" : An optional filter for all apk names that are encountered. `aabfilter` = "[*file\_filter*](../configuration/usage.md#filefilters)" : An optional filter for all aab names that are encountered. `jarfilter` = "[*file\_filter*](../configuration/usage.md#filefilters)" : An optional filter for all jar names that are encountered. `aarfilter` = "[*file\_filter*](../configuration/usage.md#filefilters)" : An optional filter for all aar names that are encountered. `warfilter` = "[*file\_filter*](../configuration/usage.md#filefilters)" : An optional filter for all war names that are encountered. `earfilter` = "[*file\_filter*](../configuration/usage.md#filefilters)" : An optional filter for all ear names that are encountered. `jmodfilter` = "[*file\_filter*](../configuration/usage.md#filefilters)" : An optional filter for all jmod names that are encountered. `zipfilter` = "[*file\_filter*](../configuration/usage.md#filefilters)" : An optional filter for all zip names that are encountered. ## Keep Modifier Attributes {: #keepmodifier} The keep tags can have the following *modifier* attributes: [**`includedescriptorclasses`**](../configuration/usage.md#includedescriptorclasses) = "*boolean*" (default = false) : Specifies whether the classes of the fields and methods specified in the keep tag must be kept as well. [**`allowshrinking`**](../configuration/usage.md#allowshrinking) = "*boolean*" (default = false) : Specifies whether the entry points specified in the keep tag may be shrunk. [**`allowoptimization`**](../configuration/usage.md#allowoptimization) = "*boolean*" (default = false) : Specifies whether the entry points specified in the keep tag may be optimized. [**`allowobfuscation`**](../configuration/usage.md#allowobfuscation) = "*boolean*" (default = false) : Specifies whether the entry points specified in the keep tag may be obfuscated. ## Class Specification Attributes and Nested Elements {: #classspecification} The keep tags can have the following *class\_specification* attributes and *class\_member\_specifications* nested elements: `access` = "*access\_modifiers*" : The optional access modifiers of the class. Any space-separated list of "public", "final", and "abstract", with optional negators "!". `annotation` = "*annotation\_name*" : The optional fully qualified name of an annotation of the class, with optional wildcards. `type` = "*type*" : The optional type of the class: one of "class", "interface", or "!interface". `name` = "*class\_name*" : The optional fully qualified name of the class, with optional wildcards. `extendsannotation` = "*annotation\_name*" : The optional fully qualified name of an annotation of the the class that the specified classes must extend, with optional wildcards. `extends` = "*class\_name*" : The optional fully qualified name of the class the specified classes must extend, with optional wildcards. `implements` = "*class\_name*" : The optional fully qualified name of the class the specified classes must implement, with optional wildcards. `` : Specifies a field. `` : Specifies a method. `` : Specifies a constructor. ## Class Member Specification Attributes {: #classmemberspecification} The class member tags can have the following *class\_member\_specification* attributes: `access` = "*access\_modifiers*" : The optional access modifiers of the class. Any space-separated list of "public", "protected", "private", "static", etc., with optional negators "!". `annotation` = "*annotation\_name*" : The optional fully qualified name of an annotation of the class member, with optional wildcards. `type` = "*type*" : The optional fully qualified type of the class member, with optional wildcards. Not applicable for constructors, but required for methods for which the **`parameters`** attribute is specified. `name` = "*name*" : The optional name of the class member, with optional wildcards. Not applicable for constructors. `parameters` = "*parameters*" : The optional comma-separated list of fully qualified method parameters, with optional wildcards. Not applicable for fields, but required for constructors, and for methods for which the **`type`** attribute is specified. `values` = "*values*" : The optional fixed value or range of values for a primitive field or method. ================================================ FILE: docs/md/manual/setup/gradle.md ================================================ **ProGuard** can be run as a task in the Java-based build tool Gradle (version 2.1 or higher). !!! android "Android projects" If you have an Android project, you can find instructions [here](gradleplugin.md). Before you can use the **`proguard`** task, you have to make sure Gradle can find it in its class path at build time. One way is to add the following line to your **`build.gradle`** file: ```Groovy buildscript { repositories { mavenCentral() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.1.0' } } ``` Please make sure the class path is set correctly for your system. You can then define a task: ```Groovy task myProguardTask(type: proguard.gradle.ProGuardTask) { ..... } ``` The embedded configuration is much like a standard ProGuard configuration. Notable similarities and differences: - Like in ProGuard configurations, we're using all lower-case names for the settings. - The options don't have a dash as prefix. - Arguments typically have quotes. - Some settings are specified as named arguments. You can find some sample build files in the **`examples/gradle`** directory of the ProGuard distribution. If you prefer a more verbose configuration derived from the Ant task, you can import the Ant task as a [Gradle task](#anttask). ## Settings {: #proguard} The ProGuard task supports the following settings in its closure: `configuration` [*files*](#file) : Read and merge options from the given ProGuard configuration files. The files are resolved and parsed lazily, during the execution phase. [**`injars`**](../configuration/usage.md#injars) [*class\_path*](#classpath) : Specifies the program jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). The files are resolved and read lazily, during the execution phase. [**`outjars`**](../configuration/usage.md#outjars) [*class\_path*](#classpath) : Specifies the names of the output jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). The files are resolved and written lazily, during the execution phase. [**`libraryjars`**](../configuration/usage.md#libraryjars) [*class\_path*](#classpath) : Specifies the names of the output jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). The files are resolved and written lazily, during the execution phase. [**`skipnonpubliclibraryclasses`**](../configuration/usage.md#skipnonpubliclibraryclasses) : Ignore non-public library classes. [**`dontskipnonpubliclibraryclassmembers`**](../configuration/usage.md#dontskipnonpubliclibraryclassmembers) : Don't ignore package visible library class members. [**`keepdirectories`**](../configuration/usage.md#keepdirectories) \['[*directory\_filter*](../configuration/usage.md#filefilters)'\] : Keep the specified directories in the output jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). [**`target`**](../configuration/usage.md#target) '*version*' : Set the given version number in the processed classes. [**`forceprocessing`**](../configuration/usage.md#forceprocessing) : Process the input, even if the output seems up to date. [**`keep`**](../configuration/usage.md#keep) \[[*modifier*,...](#keepmodifier)\] [*class\_specification*](#classspecification) : Preserve the specified classes *and* class members. [**`keepclassmembers`**](../configuration/usage.md#keepclassmembers) \[[*modifier*,...](#keepmodifier)\] [*class\_specification*](#classspecification) : Preserve the specified class members, if their classes are preserved as well. [**`keepclasseswithmembers`**](../configuration/usage.md#keepclasseswithmembers) \[[*modifier*,...](#keepmodifier)\] [*class\_specification*](#classspecification) : Preserve the specified classes *and* class members, if all of the specified class members are present. [**`keepnames`**](../configuration/usage.md#keepnames) [*class\_specification*](#classspecification) : Preserve the names of the specified classes *and* class members (if they aren't removed in the shrinking step). [**`keepclassmembernames`**](../configuration/usage.md#keepclassmembernames) [*class\_specification*](#classspecification) : Preserve the names of the specified class members (if they aren't removed in the shrinking step). [**`keepclasseswithmembernames`**](../configuration/usage.md#keepclasseswithmembernames) [*class\_specification*](#classspecification) : Preserve the names of the specified classes *and* class members, if all of the specified class members are present (after the shrinking step). [**`printseeds`**](../configuration/usage.md#printseeds) \[[*file*](#file)\] : List classes and class members matched by the various **`keep`** commands, to the standard output or to the given file. [**`dontshrink`**](../configuration/usage.md#dontshrink) : Don't shrink the input class files. [**`printusage`**](../configuration/usage.md#printusage) \[[*file*](#file)\] : List dead code of the input class files, to the standard output or to the given file. [**`whyareyoukeeping`**](../configuration/usage.md#whyareyoukeeping) [*class\_specification*](#classspecification) : Print details on why the given classes and class members are being kept in the shrinking step. [**`dontoptimize`**](../configuration/usage.md#dontoptimize) : Don't optimize the input class files. [**`optimizations`**](../configuration/usage.md#optimizations) '[*optimization\_filter*](../configuration/optimizations.md)' : Perform only the specified optimizations. [**`optimizationpasses`**](../configuration/usage.md#optimizationpasses) *n* : The number of optimization passes to be performed. [**`assumenosideeffects`**](../configuration/usage.md#assumenosideeffects) [*class\_specification*](#classspecification) : Assume that the specified methods don't have any side effects, while optimizing. *Only use this option if you know what you're doing!* [**`assumenoexternalsideeffects`**](../configuration/usage.md#assumenoexternalsideeffects) [*class\_specification*](#classspecification) : Assume that the specified methods don't have any external side effects, while optimizing. *Only use this option if you know what you're doing!* [**`assumenoescapingparameters`**](../configuration/usage.md#assumenoescapingparameters) [*class\_specification*](#classspecification) : Assume that the specified methods don't let any reference parameters escape to the heap, while optimizing. *Only use this option if you know what you're doing!* [**`assumenoexternalreturnvalues`**](../configuration/usage.md#assumenoexternalreturnvalues) [*class\_specification*](#classspecification) : Assume that the specified methods don't return any external reference values, while optimizing. *Only use this option if you know what you're doing!* [**`assumevalues`**](../configuration/usage.md#assumevalues) [*class\_specification*](#classspecification) : Assume fixed values or ranges of values for primitive fields and methods, while optimizing. *Only use this option if you know what you're doing!* [**`allowaccessmodification`**](../configuration/usage.md#allowaccessmodification) : Allow the access modifiers of classes and class members to be modified, while optimizing. [**`mergeinterfacesaggressively`**](../configuration/usage.md#mergeinterfacesaggressively) : Allow any interfaces to be merged, while optimizing. [**`dontobfuscate`**](../configuration/usage.md#dontobfuscate) : Don't obfuscate the input class files. [**`printmapping`**](../configuration/usage.md#printmapping) \[[*file*](#file)\] : Print the mapping from old names to new names for classes and class members that have been renamed, to the standard output or to the given file. [**`applymapping`**](../configuration/usage.md#applymapping) [*file*](#file) : Reuse the given mapping, for incremental obfuscation. [**`obfuscationdictionary`**](../configuration/usage.md#obfuscationdictionary) [*file*](#file) : Use the words in the given text file as obfuscated field names and method names. [**`classobfuscationdictionary`**](../configuration/usage.md#classobfuscationdictionary) [*file*](#file) : Use the words in the given text file as obfuscated class names. [**`packageobfuscationdictionary`**](../configuration/usage.md#packageobfuscationdictionary) [*file*](#file) : Use the words in the given text file as obfuscated package names. [**`overloadaggressively`**](../configuration/usage.md#overloadaggressively) : Apply aggressive overloading while obfuscating. [**`useuniqueclassmembernames`**](../configuration/usage.md#useuniqueclassmembernames) : Ensure uniform obfuscated class member names for subsequent incremental obfuscation. [**`dontusemixedcaseclassnames`**](../configuration/usage.md#dontusemixedcaseclassnames) : Don't generate mixed-case class names while obfuscating. [**`keeppackagenames`**](../configuration/usage.md#keeppackagenames) \['[*package\_filter*](../configuration/usage.md#filters)'\] : Keep the specified package names from being obfuscated. If no name is given, all package names are preserved. [**`flattenpackagehierarchy`**](../configuration/usage.md#flattenpackagehierarchy) '*package\_name*' : Repackage all packages that are renamed into the single given parent package. [**`repackageclasses`**](../configuration/usage.md#repackageclasses) \['*package\_name*'\] : Repackage all class files that are renamed into the single given package. [**`keepattributes`**](../configuration/usage.md#keepattributes) \['[*attribute\_filter*](../configuration/usage.md#filters)'\] : Preserve the specified optional Java bytecode attributes, with optional wildcards. If no name is given, all attributes are preserved. [**`keepparameternames`**](../configuration/usage.md#keepparameternames) : Keep the parameter names and types of methods that are kept. [**`renamesourcefileattribute`**](../configuration/usage.md#renamesourcefileattribute) \['*string*'\] : Put the given constant string in the **`SourceFile`** attributes. [**`adaptclassstrings`**](../configuration/usage.md#adaptclassstrings) \['[*class\_filter*](../configuration/usage.md#filters)'\] : Adapt string constants in the specified classes, based on the obfuscated names of any corresponding classes. [**`adaptresourcefilenames`**](../configuration/usage.md#adaptresourcefilenames) \['[*file\_filter*](../configuration/usage.md#filefilters)'\] : Rename the specified resource files, based on the obfuscated names of the corresponding class files. [**`adaptresourcefilecontents`**](../configuration/usage.md#adaptresourcefilecontents) \['[*file\_filter*](../configuration/usage.md#filefilters)'\] : Update the contents of the specified resource files, based on the obfuscated names of the processed classes. [**`dontpreverify`**](../configuration/usage.md#dontpreverify) : Don't preverify the processed class files if they are targeted at Java Micro Edition or at Java 6 or higher. [**`microedition`**](../configuration/usage.md#microedition) : Target the processed class files at Java Micro Edition. [**`android`**](../configuration/usage.md#android) : Target the processed class files at Android. [**`verbose`**](../configuration/usage.md#verbose) : Write out some more information during processing. [**`dontnote`**](../configuration/usage.md#dontnote) '[*class\_filter*](../configuration/usage.md#filters)' : Don't print notes about classes matching the specified class name filter. [**`dontwarn`**](../configuration/usage.md#dontwarn) '[*class\_filter*](../configuration/usage.md#filters)' : Don't print warnings about classes matching the specified class name filter. *Only use this option if you know what you're doing!* [**`ignorewarnings`**](../configuration/usage.md#ignorewarnings) : Print warnings about unresolved references, but continue processing anyhow. *Only use this option if you know what you're doing!* [**`printconfiguration`**](../configuration/usage.md#printconfiguration) \[[*file*](#file)\] : Write out the entire configuration in traditional ProGuard style, to the standard output or to the given file. Useful to replace unreadable XML configurations. [**`dump`**](../configuration/usage.md#dump) \[[*file*](#file)\] : Write out the internal structure of the processed class files, to the standard output or to the given file. [**`addconfigurationdebugging`**](../configuration/usage.md#addconfigurationdebugging) : Adds debugging information to the code, to print out ProGuard configuration suggestions at runtime. *Do not use this option in release versions.* ## Class Paths {: #classpath} Class paths are specified as Gradle file collections, which means they can be specified as simple strings, with **`files(Object)`**, etc. In addition, they can have ProGuard filters, specified as comma-separated named arguments after the file: `filter:` '[*file\_filter*](../configuration/usage.md#filefilters)' : An optional filter for all class file names and resource file names that are encountered. `apkfilter:` '[*file\_filter*](../configuration/usage.md#filefilters)' : An optional filter for all apk names that are encountered. `jarfilter:` '[*file\_filter*](../configuration/usage.md#filefilters)' : An optional filter for all jar names that are encountered. `aarfilter:` '[*file\_filter*](../configuration/usage.md#filefilters)' : An optional filter for all aar names that are encountered. `warfilter:` '[*file\_filter*](../configuration/usage.md#filefilters)' : An optional filter for all war names that are encountered. `earfilter:` '[*file\_filter*](../configuration/usage.md#filefilters)' : An optional filter for all ear names that are encountered. `zipfilter:` '[*file\_filter*](../configuration/usage.md#filefilters)' : An optional filter for all zip names that are encountered. ## Files {: #file} Files are specified as Gradle files, which means they can be specified as simple strings, as File instances, with `file(Object)`, etc. In Gradle, file names (any strings really) in double quotes can contain properties or code inside `${...}`. These are automatically expanded. For example, `"${System.getProperty('java.home')}/lib/rt.jar"` is expanded to something like `'/usr/local/java/jdk/jre/lib/rt.jar'`. Similarly, `System.getProperty('user.home')` is expanded to the user's home directory, and `System.getProperty('user.dir')` is expanded to the current working directory. ## Keep Modifiers {: #keepmodifier} The keep settings can have the following named arguments that modify their behaviors: [**`if:`**](../configuration/usage.md#if) [*class\_specification*](#classspecification) : Specifies classes and class members that must be present to activate the keep option. [**`includedescriptorclasses:`**](../configuration/usage.md#includedescriptorclasses) *boolean* (default = false) : Specifies whether the classes of the fields and methods specified in the keep tag must be kept as well. [**`allowshrinking:`**](../configuration/usage.md#allowshrinking) *boolean* (default = false) : Specifies whether the entry points specified in the keep tag may be shrunk. [**`allowoptimization:`**](../configuration/usage.md#allowoptimization) *boolean* (default = false) : Specifies whether the entry points specified in the keep tag may be optimized. [**`allowobfuscation:`**](../configuration/usage.md#allowobfuscation) *boolean* (default = false) : Specifies whether the entry points specified in the keep tag may be obfuscated. Names arguments are comma-separated, as usual. ## Class Specifications {: #classspecification} A class specification is a template of classes and class members (fields and methods). There are two alternative ways to specify such a template: 1. As a string containing a ProGuard class specification. This is the most compact and most readable way. The specification looks like a Java declaration of a class with fields and methods. For example: ```Groovy keep 'public class com.example.MyMainClass { \ public static void main(java.lang.String[]); \ }' ``` 2. As a Gradle-style setting: a method call with named arguments and a closure. This is more verbose, but it might be useful for programmatic specifications. For example: ```Groovy keep access: 'public', name: 'com.example.MyMainClass', { method access: 'public static', type: 'void', name: 'main', parameters: 'java.lang.String[]' } ``` The [ProGuard class specification](../configuration/usage.md#classspecification) is described on the traditional Usage page. A Gradle-style class specification can have the following named arguments: `access:` '*access\_modifiers*' : The optional access modifiers of the class. Any space-separated list of "public", "final", and "abstract", with optional negators "!". `annotation:` '*annotation\_name*' : The optional fully qualified name of an annotation of the class, with optional wildcards. `type:` '*type*' : The optional type of the class: one of "class", "interface", or "!interface". `name:` '*class\_name*' : The optional fully qualified name of the class, with optional wildcards. `extendsannotation:` '*annotation\_name*' : The optional fully qualified name of an annotation of the the class that the specified classes must extend, with optional wildcards. `'extends':` '*class\_name*' : The optional fully qualified name of the class the specified classes must extend, with optional wildcards. `'implements':` '*class\_name*' : The optional fully qualified name of the class the specified classes must implement, with optional wildcards. The named arguments are optional. Without any arguments, there are no constraints, so the settings match all classes. ## Gradle-style Class Member Specifications {: #classmemberspecification} The closure of a Gradle-style class specification can specify class members with these settings: `field` *field\_constraints* : Specifies a field. `method` *method\_constraints* : Specifies a method. `constructor` *constructor\_constraints* : Specifies a constructor. A class member setting can have the following named arguments to express constraints: `access:` '*access\_modifiers*' : The optional access modifiers of the class. Any space-separated list of "public", "protected", "private", "static", etc., with optional negators "!". `'annotation':` '*annotation\_name*' : The optional fully qualified name of an annotation of the class member, with optional wildcards. `type:` '*type*' : The optional fully qualified type of the class member, with optional wildcards. Not applicable for constructors, but required for methods for which the **`parameters`** argument is specified. `name:` '*name*' : The optional name of the class member, with optional wildcards. Not applicable for constructors. `parameters:` '*parameters*' : The optional comma-separated list of fully qualified method parameters, with optional wildcards. Not applicable for fields, but required for constructors, and for methods for which the **`type`** argument is specified. The named arguments are optional. Without any arguments, there are no constraints, so the settings match all constructors, fields, or methods. A class member setting doesn't have a closure. ## Alternative: imported Ant task {: #anttask} Instead of using the Gradle task, you could also integrate the Ant task in your Gradle build file: ```proguard ant.project.basedir = '../..' ant.taskdef(resource: 'proguard/ant/task.properties', classpath: '/usr/local/java/proguard/lib/proguard.jar') ``` Gradle automatically converts the elements and attributes to Groovy methods, so converting the configuration is essentially mechanical. The one-on-one mapping can be useful, but the resulting configuration is more verbose. For instance: ```proguard task proguard << { ant.proguard(printmapping: 'proguard.map', overloadaggressively: 'on', repackageclasses: '', renamesourcefileattribute: 'SourceFile') { injar(file: 'application.jar') injar(file: 'gui.jar', filter: '!META-INF/**') ..... } } ``` ================================================ FILE: docs/md/manual/setup/gradleplugin.md ================================================ This page will guide you through to the basic steps of processing your Android application or library with ProGuard. !!! tip "Java / Kotlin desktop or server projects" If you have a Java / Kotlin desktop or server project, you can find instructions [here](gradle.md). ## ProGuard Gradle Plugin (AGP version 4.x - AGP 7.x) You can add the ProGuard plugin to your project by including the following in your root level `build.gradle(.kts)` file: === "Groovy" ```Groovy buildscript { repositories { google() // For the Android Gradle plugin. mavenCentral() // For the ProGuard Gradle Plugin and anything else. } dependencies { classpath 'com.android.tools.build:gradle:x.y.z' // The Android Gradle plugin. classpath 'com.guardsquare:proguard-gradle:7.1.0' // The ProGuard Gradle plugin. } } ``` === "Kotlin" ```kotlin buildscript { repositories { google() // For the Android Gradle plugin. mavenCentral() // For the ProGuard Gradle Plugin and anything else. } dependencies { classpath("com.android.tools.build:gradle:x.y.z") // The Android Gradle plugin. classpath("com.guardsquare:proguard-gradle:7.1.0") // The ProGuard Gradle plugin. } } ``` To actually apply the plugin to your project, just add the line to your module level `build.gradle(.kts)` file after applying the Android Gradle plugin as shown below. === "Groovy" ```Groovy apply plugin: 'com.android.application' apply plugin: 'com.guardsquare.proguard' ``` === "Kotlin" ```kotlin plugins { id("com.android.application") id("proguard") } ``` ProGuard expects unobfuscated class files as input. Therefore, other obfuscators such as R8 have to be disabled. === "Groovy" ```Groovy android { ... buildTypes { release { // Deactivate R8. minifyEnabled false } } } ``` === "Kotlin" ```kotlin android { ... buildTypes { getByName("release") { // Deactivate R8. isMinifyEnabled = false } } } ``` ProGuard can be executed automatically whenever you build any of the configured variants. You can configure a variant using the `proguard` block in your module level `build.gradle(.kts)` files. This is a top-level block and should be placed outside the `android` block. For example, in the snippet below, ProGuard is configured to only process the release variant of the application, using a configuration provided by the user (`proguard-project.txt`) and a [default configuration](#default-configurations) (`proguard-android-optimize.txt`). === "Groovy" ```Groovy android { ... } proguard { configurations { release { defaultConfiguration 'proguard-android-optimize.txt' configuration 'proguard-project.txt' } } } ``` === "Kotlin" ```kotlin android { ... } proguard { configurations { register("release") { defaultConfiguration("proguard-android-optimize.txt") configuration("proguard-project.txt") } } } ``` You can then build your application as usual: === "Linux/macOS" ```sh ./gradlew assembleRelease ``` === "Windows" ``` gradlew assembleRelease ``` ### Default configurations There are three default configurations available: | Default configuration | Description | |-----------------------|-------------| | `proguard-android.txt` | ProGuard will obfuscate and shrink your application | | `proguard-android-optimize.txt` | ProGuard will obfuscate, shrink and optimize your application | | `proguard-android-debug.txt` | ProGuard will process the application without any obfuscation,
optimization or shrinking | ### Consumer rules ProGuard will apply the consumer rules provided by library dependencies. If you need to exclude these rules, you can use the `consumerRuleFilter`. #### consumerRuleFilter The `consumerRuleFilter` option allows you to specify a list of maven group and module name pairs to filter out the ProGuard consumer rules of the dependencies that match the specified group and module pairs. A group and module name pair is very similar to the maven coordinates you write when specifying the dependencies in the `dependencies` block, but without the version part. === "Groovy" ```Groovy proguard { configurations { release { consumerRuleFilter 'groupName:moduleName', 'anotherGroupName:anotherModuleName' } } } ``` === "Kotlin" ```Kotlin proguard { configurations { register("release") { consumerRuleFilter("groupName:moduleName", "anotherGroupName:anotherModuleName") } } } ``` ### Example The example [`android-plugin`](https://github.com/Guardsquare/proguard/tree/master/examples/android-plugin) has a small working Android project using the ProGuard Gradle Plugin. ## AGP Integrated ProGuard (AGP version <7) ProGuard is integrated with older versions of the Android Gradle plugin. If you have an Android Gradle project that uses such an AGP version, you can enable ProGuard instead of the default `R8` obfuscator as follows: 1. Disable R8 in your `gradle.properties`: ```ini android.enableR8=false android.enableR8.libraries=false ``` 2. Override the default version of ProGuard with the most recent one in your main `build.gradle`: ```Groovy buildscript { ... configurations.all { resolutionStrategy { dependencySubstitution { substitute module('net.sf.proguard:proguard-gradle') with module('com.guardsquare:proguard-gradle:7.1.0') } } } } ``` 3. Enable minification as usual in your `build.gradle`: ```Groovy android { ... buildTypes { release { minifyEnabled true shrinkResources true proguardFile getDefaultProguardFile('proguard-android-optimize.txt') proguardFile 'proguard-project.txt' } } } ``` There are two default configurations available when using the integrated ProGuard: | Default configuration | Description | |-----------------------|-------------| | `proguard-android.txt` | ProGuard will obfuscate and shrink your application | | `proguard-android-optimize.txt` | ProGuard will obfuscate, shrink and optimize your application | 4. Add any necessary configuration to your `proguard-project.txt`. You can then build your application as usual: === "Linux/macOS" ```sh ./gradlew assembleRelease ``` === "Windows" ``` gradlew assembleRelease ``` ### Example The example [`android-agp3-agp4`](https://github.com/Guardsquare/proguard/tree/master/examples/android-agp3-agp4) has a small working Android project for AGP < 7. ================================================ FILE: docs/md/manual/setup/standalone.md ================================================ To run ProGuard, just type: === "Linux/macOS" ```bash bin/proguard.sh ``` === "Windows" ``` bin\proguard ``` Typically, you'll put most options in a configuration file (say, `myconfig.pro`), and just call: === "Linux/macOS" ```bash bin/proguard.sh @myconfig.pro ``` === "Windows" ```bash bin\proguard @myconfig.pro ``` You can combine command line options and options from configuration files. For instance: === "Linux/macOS" ```bash bin/proguard.sh @myconfig.pro -verbose ``` === "Windows" ``` bin\proguard @myconfig.pro -verbose ``` You can add comments in a configuration file, starting with a `#` character and continuing until the end of the line. Extra whitespace between words and delimiters is ignored. File names with spaces or special characters should be quoted with single or double quotes. Options can be grouped arbitrarily in arguments on the command line and in lines in configuration files. This means that you can quote arbitrary sections of command line options, to avoid shell expansion of special characters, for instance. The order of the options is generally irrelevant. For quick experiments, you can abbreviate them to their first unique characters. All available options are described in the [Configuration section](../configuration/usage.md). ================================================ FILE: docs/md/manual/tools/appsweep.md ================================================ # AppSweep [AppSweep](https://appsweep.guardsquare.com/) is a free online app security testing tool that allows you to find and fix security issues in your Android app's code and dependencies.
![](appsweep.png)
================================================ FILE: docs/md/manual/tools/playground.md ================================================ # ProGuard Playground [ProGuard Playground](https://playground.proguard.com/) is an online rule visualizer tool compatible with ProGuard, R8 and DexGuard keep rules. It allows you to write keep rules and in real-time see which entities in your app will be matched by those rules. ================================================ FILE: docs/md/manual/tools/retrace.md ================================================ # ReTrace **ReTrace** is a companion tool for ProGuard and DexGuard that 'de-obfuscates' stack traces. When an obfuscated program throws an exception, the resulting stack trace typically isn't very informative. Class names and method names have been replaced by short meaningless strings. Source file names and line numbers are missing altogether. While this may be intentional, it can also be inconvenient when debugging problems. ReTrace deobfuscation workflow ReTrace can read an obfuscated stack trace and restore it to what it would look like without obfuscation. The restoration is based on the mapping file that an obfuscator (like ProGuard, DexGuard or R8) can write out while obfuscating. The mapping file links the original class names and class member names to their obfuscated names. ## Usage {: #usage } You can find the ReTrace jar in the `lib` directory of the ProGuard distribution. To run ReTrace, just type: `java -jar retrace.jar `\[*options...*\] *mapping\_file* \[*stacktrace\_file*\] Alternatively, the `bin` directory contains some short Linux and Windows scripts containing this command. These are the arguments: *mapping\_file* : Specifies the name of the mapping file. *stacktrace\_file* : Optionally specifies the name of the file containing the stack trace. If no file is specified, a stack trace is read from the standard input. The stack trace must be encoded with UTF-8 encoding. Blank lines and unrecognized lines are ignored. The following options are supported: `-verbose` : Specifies to print out more informative stack traces that include not only method names, but also method return types and arguments. `-regex` *regular\_expression* : Specifies the regular expression that is used to parse the lines in the stack trace. Specifying a different regular expression allows to de-obfuscate more general types of input than just stack traces. A relatively simple expression like this works for basic stack trace formats: (?:.*? at %c\.%m\(%s(?::%l)?\))|(?:(?:.*?[:"] +)?%c(?::.*)?) It for instance matches the following lines: Exception in thread "main" myapplication.MyException: Some message at com.example.MyClass.myMethod(MyClass.java:123) The regular expression is a Java regular expression (cfr. the documentation of `java.util.regex.Pattern`), with a few additional wildcards: | Wildcard | Description | Example |----------|--------------------------------------------|------------------------------------------- | `%c` | matches a class name | `com.example.MyClass` | `%C` | matches a class name with slashes | `com/example/MyClass` | `%t` | matches a field type or method return type | `com.example.MyClass[]` | `%f` | matches a field name | `myField` | `%m` | matches a method name | `myMethod` | `%a` | matches a list of method arguments | `boolean,int` | `%s` | matches a source file name | `MyClass.java` | `%l` | matches a line number inside a method | `123` Elements that match these wildcards are de-obfuscated, when possible. Note that regular expressions must not contain any capturing groups. Use non-capturing groups instead: `(?:`...`)` You can print out the default regular expression by running ReTrace without arguments. It also matches more complex stack traces. The restored stack trace is printed to the standard output. The completeness of the restored stack trace depends on the presence of line number tables in the obfuscated class files: - If all line numbers have been preserved while obfuscating the application, ReTrace will be able to restore the stack trace completely. - If the line numbers have been removed, mapping obfuscated method names back to their original names has become ambiguous. Retrace will list all possible original method names for each line in the stack trace. The user can then try to deduce the actual stack trace manually, based on the logic of the program. Source file names are currently restored based on the names of the outer-most classes. If you prefer to keep the obfuscated name, you can replace `%s` in the default regular expression by `.*` Unobfuscated elements and obfuscated elements for which no mapping is available will be left unchanged. ## Examples {: #examples } ### Restoring a stack trace with line numbers {: #with} Assume for instance an application has been obfuscated using the following extra options: -printmapping mapping.txt -renamesourcefileattribute MyApplication -keepattributes SourceFile,LineNumberTable Now assume the processed application throws an exception: java.io.IOException: Can't read [dummy.jar] (No such file or directory) at proguard.y.a(MyApplication:188) at proguard.y.a(MyApplication:158) at proguard.y.a(MyApplication:136) at proguard.y.a(MyApplication:66) at proguard.ProGuard.c(MyApplication:218) at proguard.ProGuard.a(MyApplication:82) at proguard.ProGuard.main(MyApplication:538) Caused by: java.io.IOException: No such file or directory at proguard.d.q.a(MyApplication:50) at proguard.y.a(MyApplication:184) ... 6 more If we have saved the stack trace in a file `stacktrace.txt`, we can use the following command to recover the stack trace: retrace mapping.txt stacktrace.txt The output will correspond to the original stack trace: java.io.IOException: Can't read [dummy.jar] (No such file or directory) at proguard.InputReader.readInput(InputReader.java:188) at proguard.InputReader.readInput(InputReader.java:158) at proguard.InputReader.readInput(InputReader.java:136) at proguard.InputReader.execute(InputReader.java:66) at proguard.ProGuard.readInput(ProGuard.java:218) at proguard.ProGuard.execute(ProGuard.java:82) at proguard.ProGuard.main(ProGuard.java:538) Caused by: java.io.IOException: No such file or directory at proguard.io.DirectoryPump.pumpDataEntries(DirectoryPump.java:50) at proguard.InputReader.readInput(InputReader.java:184) ... 6 more ### Restoring a stack trace with line numbers (verbose) {: #withverbose} In the previous example, we could also use the verbose flag: java -jar retrace.jar -verbose mapping.txt stacktrace.txt The output will then look as follows: java.io.IOException: Can't read [dummy.jar] (No such file or directory) at proguard.InputReader.void readInput(java.lang.String,proguard.ClassPathEntry,proguard.io.DataEntryReader)(InputReader.java:188) at proguard.InputReader.void readInput(java.lang.String,proguard.ClassPath,int,int,proguard.io.DataEntryReader)(InputReader.java:158) at proguard.InputReader.void readInput(java.lang.String,proguard.ClassPath,proguard.io.DataEntryReader)(InputReader.java:136) at proguard.InputReader.void execute(proguard.classfile.ClassPool,proguard.classfile.ClassPool)(InputReader.java:66) at proguard.ProGuard.void readInput()(ProGuard.java:218) at proguard.ProGuard.void execute()(ProGuard.java:82) at proguard.ProGuard.void main(java.lang.String[])(ProGuard.java:538) Caused by: java.io.IOException: No such file or directory at proguard.io.DirectoryPump.void pumpDataEntries(proguard.io.DataEntryReader)(DirectoryPump.java:50) at proguard.InputReader.void readInput(java.lang.String,proguard.ClassPathEntry,proguard.io.DataEntryReader)(InputReader.java:184) ... 6 more ### Restoring a stack trace without line numbers {: #without} Assume for instance the application has been obfuscated using the following extra options, this time without preserving the line number tables: -printmapping mapping.txt A stack trace `stacktrace.txt` will then lack line number information, showing "Unknown source" instead: java.io.IOException: Can't read [dummy.jar] (No such file or directory) at proguard.y.a(Unknown Source) at proguard.y.a(Unknown Source) at proguard.y.a(Unknown Source) at proguard.y.a(Unknown Source) at proguard.ProGuard.c(Unknown Source) at proguard.ProGuard.a(Unknown Source) at proguard.ProGuard.main(Unknown Source) Caused by: java.io.IOException: No such file or directory at proguard.d.q.a(Unknown Source) ... 7 more We can still use the same command to recover the stack trace: java -jar retrace.jar mapping.txt stacktrace.txt The output will now list all alternative original method names for each ambiguous obfuscated method name: java.io.IOException: Can't read [dummy.jar] (No such file or directory) at proguard.InputReader.execute(InputReader.java) readInput(InputReader.java) at proguard.InputReader.execute(InputReader.java) readInput(InputReader.java) at proguard.InputReader.execute(InputReader.java) readInput(InputReader.java) at proguard.InputReader.execute(InputReader.java) readInput(InputReader.java) at proguard.ProGuard.readInput(ProGuard.java) at proguard.ProGuard.execute(ProGuard.java) optimize(ProGuard.java) createPrintStream(ProGuard.java) closePrintStream(ProGuard.java) fileName(ProGuard.java) at proguard.ProGuard.main(ProGuard.java) Caused by: java.io.IOException: No such file or directory at proguard.io.DirectoryPump.pumpDataEntries(DirectoryPump.java) readFiles(DirectoryPump.java) For instance, ReTrace can't tell if the method `a` corresponds to `execute` or to `readInput`, so it lists both. You need to figure it out based on your knowledge of the application. Having line numbers and unambiguous names clearly is a lot easier, so you should consider [preserving the line numbers](../configuration/examples.md#stacktrace) when you obfuscate your application. ## Specifications {: #specifications } A mapping file contains the original names and the obfuscated names of classes, fields, and methods. ProGuard can write out such a file while obfuscating an application or a library, with the option [`-printmapping`](../configuration/usage.md#printmapping). ReTrace requires the mapping file to restore obfuscated stack traces to more readable versions. It is a readable file with UTF-8 encoding, so you can also look up names in an ordinary text viewer. The format is pretty self-explanatory, but we describe its details here. A mapping file contains a sequence of records of the following form: classline fieldline * methodline * A `classline`, with a trailing colon, specifies a class and its obfuscated name: originalclassname -> obfuscatedclassname: A `fieldline`, with 4 leading spaces, specifies a field and its obfuscated name: originalfieldtype originalfieldname -> obfuscatedfieldname A `methodline`, with 4 leading spaces, specifies a method and its obfuscated name: [startline:endline:]originalreturntype [originalclassname.]originalmethodname(originalargumenttype,...)[:originalstartline[:originalendline]] -> obfuscatedmethodname - An asterisk "*" means the line may occur any number of times. - Square brackets "\[\]" mean that their contents are optional. - Ellipsis dots "..." mean that any number of the preceding items may be specified. - The colon ":", the separator ".", and the arrow "->" are literal tokens. ### Example The following snippet gives an impression of the structure of a mapping file: com example.application.ArgumentWordReader -> com.example.a.a: java.lang.String[] arguments -> a int index -> a 36:57:void (java.lang.String[],java.io.File) -> 64:64:java.lang.String nextLine() -> a 72:72:java.lang.String lineLocationDescription() -> b com.example.application.Main -> com.example.application.Main: com.example.application.Configuration configuration -> a 50:66:void (com.example.application.Configuration) -> 74:228:void execute() -> a 2039:2056:void com.example.application.GPL.check():39:56 -> a 2039:2056:void execute():76 -> a 2236:2252:void printConfiguration():236:252 -> a 2236:2252:void execute():80 -> a 3040:3042:java.io.PrintWriter com.example.application.util.PrintWriterUtil.createPrintWriterOut(java.io.File):40:42 -> a 3040:3042:void printConfiguration():243 -> a 3040:3042:void execute():80 -> a 3260:3268:void readInput():260:268 -> a 3260:3268:void execute():97 -> a You can see the names of classes and their fields and methods: - The fields and methods are listed in ProGuard configuration format (javap format), with descriptors that have return types and argument types but no argument names. In the above example: void (java.lang.String[],java.io.File) refers to a constructor with a `String` array argument and a `File` argument. - A method may have a leading line number range, if it is known from the original source code (see [Producing useful obfuscated stack traces](../configuration/examples.md#stacktrace) in the Examples section). Unlike method names, line numbers are unique within a class, so ReTrace can resolve lines in a stack trace without ambiguities. For example: 74:228:void execute() refers to a method `execute`, defined on lines 74 to 228. - The obfuscated method name follows the arrow. For example: 74:228:void execute() -> a shows that method `execute` has been renamed to `a`. Multiple fields and methods can get the same obfuscated names, as long as their descriptors are different. ### Inlined methods The mapping file accounts for the added complexity of inlined methods (as of ProGuard/ReTrace version 5.2). The optimization step may inline methods into other methods — recursively even. A single line in an obfuscated stack trace can then correspond to multiple lines in the original stack trace: the line that throws the exception followed by one or more nested method calls. In such cases, the mapping file repeats the leading line number range on subsequent lines. For example: 3040:3042:java.io.PrintWriter com.example.application.util.PrintWriterUtil.createPrintWriterOut(java.io.File):40:42 -> a 3040:3042:void printConfiguration():243 -> a 3040:3042:void execute():80 -> a - The subsequent lines correspond to the subsequent lines of the original stack trace. For example: 3040:3042:java.io.PrintWriter com.example.application.util.PrintWriterUtil.createPrintWriterOut(java.io.File):40:42 -> a 3040:3042:void printConfiguration():243 -> a 3040:3042:void execute():80 -> a refers to method `createPrintWriterOut` called from and inlined in `printConfiguration`, in turn called from and inlined in method `execute`. - An original method name may have a preceding class name, if the method originates from a different class. For example: 3040:3042:java.io.PrintWriter com.example.application.util.PrintWriterUtil.createPrintWriterOut(java.io.File):40:42 -> a shows that method `createPrintWriterOut` was originally defined in class `PrintWriterUtil`. - A single trailing line number corresponds to an inlined method call. For example: 3040:3042:java.io.PrintWriter com.example.application.util.PrintWriterUtil.createPrintWriterOut(java.io.File):40:42 -> a 3040:3042:void printConfiguration():243 -> a 3040:3042:void execute():80 -> a specifies that method `execute` called `printConfiguration` on line 80, and `printconfiguration` called `createPrintWriterOut` on line 243. - A traling line number range corresponds to the final inlined method body. For example: 3040:3042:java.io.PrintWriter com.example.application.util.PrintWriterUtil.createPrintWriterOut(java.io.File):40:42 -> a shows that method `createPrintWriterOut` covered lines 40 to 42. - The leading line number range is synthetic, to avoid ambiguities with other code in the same class. ProGuard makes up the range, but tries to make it similar-looking to the original code (by adding offsets that are multiples of 1000), for convenience. For example: 3040:3042:java.io.PrintWriter com.example.application.util.PrintWriterUtil.createPrintWriterOut(java.io.File):40:42 -> a created synthetic range 3040:3042 in the bytecode of class `Main` to be unique but still resemble source code range 40:42 in class `PrintWriterUtil`. Tools that don't account for these repeated line number ranges, like older versions of ReTrace, may still degrade gracefully by outputting the subsequent lines without interpreting them. ================================================ FILE: docs/md/manual/troubleshooting/limitations.md ================================================ When using ProGuard, you should be aware of a few technical issues, all of which are easily avoided or resolved: - For best results, ProGuard's optimization algorithms assume that the processed code never **intentionally throws NullPointerExceptions** or ArrayIndexOutOfBoundsExceptions, or even OutOfMemoryErrors or StackOverflowErrors, in order to achieve something useful. For instance, it may remove a method call `myObject.myMethod()` if that call wouldn't have any effect. It ignores the possibility that `myObject` might be null, causing a NullPointerException. In some way this is a good thing: optimized code may throw fewer exceptions. Should this entire assumption be false, you'll have to switch off optimization using the `-dontoptimize` option. - ProGuard's optimization algorithms currently also assume that the processed code never creates **busy-waiting loops** without at least testing on a volatile field. Again, it may remove such loops. Should this assumption be false, you'll have to switch off optimization using the `-dontoptimize` option. ================================================ FILE: docs/md/manual/troubleshooting/troubleshooting.md ================================================ While preparing a configuration for processing your code, you may bump into a few problems. The following sections discuss some common issues and solutions: !!! tip Whenever you don't find the solution for your issue on this page, the [***Guardsquare Community***](https://community.guardsquare.com/) might be the place to check for an answer or post a question. ## Problems while processing {: #processing} ProGuard may print out some notes and non-fatal warnings: **Note: can't find dynamically referenced class ...** {: #dynamicalclass} : ProGuard can't find a class or interface that your code is accessing by means of introspection. You should consider adding the jar that contains this class. **Note: ... calls '(...)Class.forName(variable).newInstance()'** {: #dynamicalclasscast} : Your code uses reflection to dynamically create class instances, with a construct like "`(MyClass)Class.forName(variable).newInstance()`". Depending on your application, you may need to keep the mentioned classes with an option like "`-keep class MyClass`", or their implementations with an option like "`-keep class * implements MyClass`". You can switch off these notes by specifying the [`-dontnote`](../configuration/usage.md#dontnote) option. **Note: ... accesses a field/method '...' dynamically** {: #dynamicalclassmember} : Your code uses reflection to find a fields or a method, with a construct like "`.getField("myField")`". Depending on your application, you may need to figure out where the mentioned class members are defined and keep them with an option like "`-keep class MyClass { MyFieldType myField; }`". Otherwise, ProGuard might remove or obfuscate the class members, since it can't know which ones they are exactly. It does list possible candidates, for your information. You can switch off these notes by specifying the [`-dontnote`](../configuration/usage.md#dontnote) option. **Note: ... calls 'Class.get...'**, **'Field.get...'**, or **'Method.get...'** {: #attributes} : Your code uses reflection to access metadata from the code, with an invocation like "`class.getAnnotations()`". You then generally need to preserve optional [class file attributes](../configuration/attributes.md), which ProGuard removes by default. The attributes contain information about annotations, enclosing classes, enclosing methods, etc. In a summary in the log, ProGuard provides a suggested configuration, like [`-keepattributes *Annotation*`](../configuration/usage.md#keepattributes). If you're sure the attributes are not necessary, you can switch off these notes by specifying the [`-dontnote`](../configuration/usage.md#dontnote) option. **Note: the configuration refers to the unknown class '...'** {: #unknownclass} : Your configuration refers to the name of a class that is not present in the program jars or library jars. You should check whether the name is correct. Notably, you should make sure that you always specify fully-qualified names, not forgetting the package names. **Note: the configuration keeps the entry point '...', but not the descriptor class '...'** {: #descriptorclass} : Your configuration contains a [`-keep`](../configuration/usage.md#keep) option to preserve the given method (or field), but no `-keep` option for the given class that is an argument type or return type in the method's descriptor. You may then want to keep the class too. Otherwise, ProGuard will obfuscate its name, thus changing the method's signature. The method might then become unfindable as an entry point, e.g. if it is part of a public API. You can automatically keep such descriptor classes with the `-keep` option modifier [`includedescriptorclasses`](../configuration/usage.md#includedescriptorclasses) (`-keep,includedescriptorclasses` ...). You can switch off these notes by specifying the [`-dontnote`](../configuration/usage.md#dontnote) option. **Note: the configuration explicitly specifies '...' to keep library class '...'** {: #libraryclass} : Your configuration contains a [`-keep`](../configuration/usage.md#keep) option to preserve the given library class. However, you don't need to keep any library classes. ProGuard always leaves underlying libraries unchanged. You can switch off these notes by specifying the [`-dontnote`](../configuration/usage.md#dontnote) option. **Note: the configuration doesn't specify which class members to keep for class '...'** {: #classmembers} : Your configuration contains a [`-keepclassmembers`](../configuration/usage.md#keepclassmembers)/[`-keepclasseswithmembers`](../configuration/usage.md#keepclasseswithmembers) option to preserve fields or methods in the given class, but it doesn't specify which fields or methods. This way, the option simply won't have any effect. You probably want to specify one or more fields or methods, as usual between curly braces. You can specify all fields or methods with a wildcard "`*;`". You should also consider if you just need the more common [`-keep`](../configuration/usage.md#keep) option, which preserves all specified classes *and* class members. The [overview of all `keep` options](../configuration/usage.md#keepoverview) can help. You can switch off these notes by specifying the [`-dontnote`](../configuration/usage.md#dontnote) option. **Note: the configuration specifies that none of the methods of class '...' have any side effects** {: #nosideeffects} : Your configuration contains an option [`-assumenosideeffects`](../configuration/usage.md#assumenosideeffects) to indicate that the specified methods don't have any side effects. However, the configuration tries to match *all* methods, by using a wildcard like "`*;`". This includes methods from `java.lang.Object`, such as `wait()` and `notify()`. Removing invocations of those methods will most likely break your application. You should list the methods without side effects more conservatively. You can switch off these notes by specifying the [`-dontnote`](../configuration/usage.md#dontnote) option. **Note: duplicate definition of program/library class** {: #duplicateclass} : Your program jars or library jars contain multiple definitions of the listed classes. ProGuard continues processing as usual, only considering the first definitions. The warning may be an indication of some problem though, so it's advisable to remove the duplicates. A convenient way to do so is by specifying filters on the input jars or library jars. You can switch off these notes by specifying the [`-dontnote`](../configuration/usage.md#dontnote) option. **Warning: can't write resource ... Duplicate zip entry** {: #duplicatezipentry} : Your input jars contain multiple resource files with the same name. ProGuard continues copying the resource files as usual, skipping any files with previously used names. Once more, the warning may be an indication of some problem though, so it's advisable to remove the duplicates. A convenient way to do so is by specifying filters on the input jars. There is no option to switch off these warnings. ProGuard may terminate when it encounters parsing errors or I/O errors, or some more serious warnings: **Warning: can't find superclass or interface**
**Warning: can't find referenced class** {: #unresolvedclass} : A class in one of your program jars or library jars is referring to a class or interface that is missing from the input. The warning lists both the referencing class(es) and the missing referenced class(es). There can be a few reasons, with their own solutions: 1. If the missing class is referenced from your own code, you may have forgotten to specify an essential library. Just like when compiling all code from scratch, you must specify all libraries that the code is referencing, directly or indirectly. If the library should be processed and included in the output, you should specify it with [`-injars`](../configuration/usage.md#injars), otherwise you should specify it with [`-libraryjars`](../configuration/usage.md#libraryjars). For example, if ProGuard complains that it can't find a `java.lang` class, you have to make sure that you are specifying the run-time library of your platform. For JSE, these are typically packaged in `lib/rt.jar` (`vm.jar` for IBM's JVM, and `classes.jar` in MacOS X) or as of Java 9, `jmods/java.base.jmod`. 2. If the missing class is referenced from a pre-compiled third-party library, and your original code runs fine without it, then the missing dependency doesn't seem to hurt. The cleanest solution is to [filter out](../configuration/usage.md#filters) the *referencing* class or classes from the input, with a filter like "`-injars myapplication.jar(!somepackage/SomeUnusedReferencingClass.class)`". DexGuard will then skip this class entirely in the input, and it will not bump into the problem of its missing reference. However, you may then have to filter out other classes that are in turn referencing the removed class. In practice, this works best if you can filter out entire unused packages at once, with a wildcard filter like "`-libraryjars mylibrary.jar(!someunusedpackage/**)`". 3. If you don't feel like filtering out the problematic classes, you can try your luck with the [`-ignorewarnings`](../configuration/usage.md#ignorewarnings) option, or even the [`-dontwarn`](../configuration/usage.md#dontwarn) option. Only use these options if you really know what you're doing though. **Error: Can't find any super classes of ... (not even immediate super class ...)** **Error: Can't find common super class of ... and ...** {: #superclass} : It seems like you tried to avoid the warnings from the previous paragraph by specifying [`-ignorewarnings`](../configuration/usage.md#ignorewarnings) or [`-dontwarn`](../configuration/usage.md#dontwarn), but it didn't work out. ProGuard's optimization step and preverification step really need the missing classes to make sense of the code. Preferably, you would solve the problem by adding the missing library, as discussed. If you're sure the class that references the missing class isn't used either, you could also try filtering it out from the input, by adding a filter to the corresponding [`-injars`](../configuration/usage.md#injars) option: "`-injars myapplication.jar(!somepackage/SomeUnusedClass.class)`". As a final solution, you could switch off optimization ([`-dontoptimize`](../configuration/usage.md#dontoptimize)) and preverification ([`-dontpreverify`](../configuration/usage.md#dontpreverify)). **Warning: can't find referenced field/method '...' in program class ...** {: #unresolvedprogramclassmember} : A program class is referring to a field or a method that is missing from another program class. The warning lists both the referencing class and the missing referenced class member. Your compiled class files are most likely inconsistent. Possibly, some class file didn't get recompiled properly, or some class file was left behind after its source file was removed. Try removing all compiled class files and rebuilding your project. **Warning: can't find referenced field/method '...' in library class ...** {: #unresolvedlibraryclassmember} : A program class is referring to a field or a method that is missing from a library class. The warning lists both the referencing class and the missing referenced class member. Your compiled class files are inconsistent with the libraries. You may need to recompile the class files, or otherwise upgrade the libraries to consistent versions. 1. If there are unresolved references to class members in *program classes*, your compiled class files are most likely inconsistent. Possibly, some class file didn't get recompiled properly, or some class file was left behind after its source file was removed. Try removing all compiled class files and rebuilding your project. 2. If there are unresolved references to class members in *library classes*, your compiled class files are inconsistent with the libraries. You may need to recompile the class files, or otherwise upgrade the libraries to consistent versions. Alternatively, you may get away with ignoring the inconsistency with the options [`-ignorewarnings`](../configuration/usage.md#ignorewarnings) or even [`-dontwarn`](../configuration/usage.md#dontwarn). **Warning: can't find enclosing class/method** {: #unresolvedenclosingmethod} : If there are unresolved references to classes that are defined inside methods in your input, once more, your compiled class files are most likely inconsistent. Possibly, some class file didn't get recompiled properly, or some class file was left behind after its source file was removed. Try removing all compiled class files and rebuilding your project. **Warning: library class ... depends on program class ...** {: #dependency} : If any of your library classes depend on your program classes, by extending, implementing or just referencing them, your processed code will generally be unusable. Program classes can depend on library classes, but not the other way around. Program classes are processed, while library classes always remain unchanged. It is therefore impossible to adapt references from library classes to program classes, for instance if the program classes are renamed. You should define a clean separation between program code (specified with [`-injars`](../configuration/usage.md#injars)) and library code (specified with [`-libraryjars`](../configuration/usage.md#libraryjars)), and try again. **Warning: class file ... unexpectedly contains class ...** {: #unexpectedclass} : The given class file contains a definition for the given class, but the directory name of the file doesn't correspond to the package name of the class. ProGuard will accept the class definition, but the current implementation will not write out the processed version. Please make sure your input classes are packaged correctly. Notably, class files that are in the `WEB-INF/classes` directory in a war should be packaged in a jar and put in the `WEB-INF/lib` directory. If you don't mind these classes not being written to the output, you can specify the [`-ignorewarnings`](../configuration/usage.md#ignorewarnings) option, or even the [`-dontwarn`](../configuration/usage.md#dontwarn) option. **Warning: ... is not being kept as ..., but remapped to ...** {: #mappingconflict1} : There is a conflict between a [`-keep`](../configuration/usage.md#keep) option and the mapping file specified with an [`-applymapping`](../configuration/usage.md#applymapping) option, in the obfuscation step. The given class name or class member name can't be kept by its original name, as specified in the configuration, but it has to be mapped to the other given name, as specified in the mapping file. You should adapt your configuration or your mapping file to remove the conflict. Alternatively, if you're sure the renaming won't hurt, you can specify the [`-ignorewarnings`](../configuration/usage.md#ignorewarnings) option, or even the [`-dontwarn`](../configuration/usage.md#dontwarn) option. **Warning: field/method ... can't be mapped to ...** {: #mappingconflict2} : There is a conflict between some new program code and the mapping file specified with an [`-applymapping`](../configuration/usage.md#applymapping) option, in the obfuscation step. The given class member can't be mapped to the given name, because it would conflict with another class member that is already being mapped to the same name. This can happen if you are performing incremental obfuscation, applying an obfuscation mapping file from an initial obfuscation step. For instance, some new class may have been added that extends two existing classes, introducing a conflict in the name space of its class members. If you're sure the class member receiving another name than the one specified won't hurt, you can specify the [`-ignorewarnings`](../configuration/usage.md#ignorewarnings) option, or even the [`-dontwarn`](../configuration/usage.md#dontwarn) option. Note that you should always use the [`-useuniqueclassmembernames`](../configuration/usage.md#useuniqueclassmembernames) option in the initial obfuscation step, in order to reduce the risk of conflicts. **Error: Unsupported class version number** {: #unsupportedclassversion} : You are trying to process class files compiled for a recent version of Java that your copy of ProGuard doesn't support yet. You should [check on-line](https://github.com/Guardsquare/proguard/releases) if there is a more recent release. **Error: You have to specify [`-keep`](../configuration/usage.md#keep) options** : You either forgot to specify [`-keep`](../configuration/usage.md#keep) options, or you mistyped the class names. ProGuard has to know exactly what you want to keep: an application, an applet, a servlet, a midlet,..., or any combination of these. Without the proper seed specifications, ProGuard would shrink, optimize, or obfuscate all class files away. **Error: Expecting class path separator ';' before 'Files\Java\\**...**'** (in Windows) : If the path of your run-time jar contains spaces, like in "Program Files", you have to enclose it with single or double quotes, as explained in the section on [file names](../configuration/usage.md#filename). This is actually true for all file names containing special characters, on all platforms. **Error: Can't read [**...**/lib/rt.jar\] (No such file or directory)** : In MacOS X, the run-time classes may be in a different place than on most other platforms. You'll then have to adapt your configuration, replacing the path `/lib/rt.jar` by `/../Classes/classes.jar`. As of Java 9, the runtime classes are packaged in `/jmods/java.base.jmod` and other modules next to it. **Error: Can't read ...** {: #cantread} : ProGuard can't read the specified file or directory. Double-check that the name is correct in your configuration, that the file is readable, and that it is not corrupt. An additional message "Unexpected end of ZLIB input stream" suggests that the file is truncated. You should then make sure that the file is complete on disk when ProGuard starts (asynchronous copying? unflushed buffer or cache?), and that it is not somehow overwritten by ProGuard's own output. **Error: Can't write ...** {: #cantwrite} : ProGuard can't write the specified file or directory. Double-check that the name is correct in your configuration and that the file is writable. **Internal problem starting the ProGuard GUI (Cannot write XdndAware property)** (in Linux) : In Linux, at least with Java 6, the GUI may not start properly, due to [Sun Bug \#7027598](http://bugs.sun.com/view_bug.do?bug_id=7027598). The work-around at this time is to specify the JVM option `-DsuppressSwingDropSupport=true` when running the GUI. Should ProGuard crash while processing your application: **OutOfMemoryError** {: #outofmemoryerror} : You can try increasing the heap size of the Java virtual machine, with the usual `-Xmx` option: - In Java, specify the option as an argument to the JVM: `java -Xmx1024m` - In Ant, set the environment variable `ANT_OPTS=-Xmx1024m` - In Gradle, set the environment variable `GRADLE_OPTS=-Xmx1024m` - In Maven, set the environment variable `MAVEN_OPTS=-Xmx1024m` - In Eclipse, add the line `-Xmx1024m` to the file `eclipse.ini` inside your Eclipse install. You can also reduce the amount of memory that ProGuard needs by removing unnecessary library jars from your configuration, or by filtering out unused library packages and classes. **StackOverflowError** {: #stackoverflowerror} : This error might occur when processing a large code base on Windows (surprisingly, not so easily on Linux). In theory, increasing the stack size of the Java virtual machine (with the usual `-Xss` option) should help too. In practice however, the `-Xss` setting doesn't have any effect on the main thread, due to [Sun Bug \#4362291](http://bugs.sun.com/view_bug.do?bug_id=4362291). As a result, this solution will only work when running ProGuard in a different thread, e.g. from its GUI. **Unexpected error** {: #unexpectederror} : ProGuard has encountered an unexpected condition, typically in the optimization step. It may or may not recover. You should be able to avoid it using the [`-dontoptimize`](../configuration/usage.md#dontoptimize) option. In any case, please report the problem, preferably with the simplest example that causes ProGuard to crash. **Otherwise...** {: #otherwise} : Maybe your class files are corrupt. See if recompiling them and trying again helps. If not, please report the problem, preferably with the simplest example that causes ProGuard to crash. ## Unexpected observations after processing {: #afterprocessing} If ProGuard seems to run fine, but your processed code doesn't look right, there might be a couple of reasons: **Disappearing classes** {: #disappearingclasses} : If you are working on Windows and it looks like some classes have disappeared from your output, you should make sure you're not writing your output class files to a directory (or unpacking the output jar). On platforms with case-insensitive file systems, such as Windows, unpacking tools often let class files with similar lower-case and upper-case names overwrite each other. If you really can't switch to a different operating system, you could consider using ProGuard's [`-dontusemixedcaseclassnames`](../configuration/usage.md#dontusemixedcaseclassnames) option. Also, you should make sure your class files are in directories that correspond to their package names. ProGuard will read misplaced class files, but it will currently not write their processed versions. Notably, class files that are in the `WEB-INF/classes` directory in a war should be packaged in a jar and put in the `WEB-INF/lib` directory. **Classes or class members not being kept** {: #notkept} : If ProGuard is not keeping the right classes or class members, make sure you are using fully qualified class names. If the package name of some class is missing, ProGuard won't match the elements that you might be expecting. It may help to double-check for typos too. You can use the [`-printseeds`](../configuration/usage.md#printseeds) option to see which elements are being kept exactly. If you are using marker interfaces to keep other classes, the marker interfaces themselves are probably being removed in the shrinking step. You should therefore always explicitly keep any marker interfaces, with an option like "`-keep interface MyMarkerInterface`". Similarly, if you are keeping classes based on annotations, you may have to avoid that the annotation classes themselves are removed in the shrinking step. You should package the annotation classes as a library, or explicitly keep them in your program code with an option like "`-keep @interface *`". **Class names not being obfuscated** {: #classnamesnotobfuscated} : If the names of some classes in your obfuscated code aren't obfuscated, you should first check all your configuration files. Chances are that some `-keep` option is preserving the original names. These options may be hiding in your own configuration files or in configuration files from libraries. **Field names not being obfuscated** {: #fieldnamesnotobfuscated} : If the names of some fields in your obfuscated code aren't obfuscated, this may be due to `-keep` options preserving the original names, for the sake of libraries like GSON. Such libraries perform reflection on the fields. If the names were obfuscated, the resulting JSON strings would come out obfuscated as well, which generally breaks persistence of the data or communication with servers. **Method names not being obfuscated** {: #methodnamesnotobfuscated} : If the names of some methods in your obfuscated code aren't obfuscated, this is most likely because they extend or implement method names in the underlying runtime libraries. Since the runtime libraries are not obfuscated, any corresponding names in the application code can't be obfuscated either, since they must remain consistent. **Variable names not being obfuscated** {: #variablenamesnotobfuscated} : If the names of the local variables and parameters in your obfuscated code don't look obfuscated, because they suspiciously resemble the names of their types, it's probably because the decompiler that you are using is coming up with those names. ProGuard's obfuscation step does remove the original names entirely, unless you explicitly keep the `LocalVariableTable` or `LocalVariableTypeTable` attributes. ## Problems while preverifying for Java Micro Edition If ProGuard seems to run fine, but the external preverifier subsequently produces errors, it's usually for a single reason: **InvalidClassException**, **class loading error**, or **verification error** : If you get any such message from the preverifier, you are probably working on a platform with a case-insensitive file system, such as Windows. The `preverify` tool always unpacks the jars, so class files with similar lower-case and upper-case names overwrite each other. You can use ProGuard's [`-dontusemixedcaseclassnames`](../configuration/usage.md#dontusemixedcaseclassnames) option to work around this problem. If the above doesn't help, there is probably a bug in the optimization step of ProGuard. Make sure you are using the latest version. You should be able to work around the problem by using the [`-dontoptimize`](../configuration/usage.md#dontoptimize) option. You can check the bug database to see if it is a known problem (often with a fix). Otherwise, please report it, preferably with the simplest example on which you can find ProGuard to fail. Note that it is no longer necessary to use an external preverifier. With the [`-microedition`](../configuration/usage.md#microedition) option, ProGuard will preverify the class files for Java Micro Edition. ## Problems at run-time {: #runtime} If ProGuard runs fine, but your processed application doesn't work, there might be several reasons: **Stack traces without class names or line numbers** {: #stacktraces} : If your stack traces don't contain any class names or lines numbers, even though you are keeping the proper attributes, make sure this debugging information is present in your compiled code to start with. Notably the Ant javac task has debugging information switched off by default. **NoClassDefFoundError** {: #noclassdeffounderror} : Your class path is probably incorrect. It should at least contain all library jars and, of course, your processed program jar. **ClassNotFoundException** {: #classnotfoundexception} : Your code is probably calling `Class.forName`, trying to create the missing class dynamically. ProGuard can only detect constant name arguments, like `Class.forName("com.example.MyClass")`. For variable name arguments like `Class.forName(someClass)`, you have to keep all possible classes using the appropriate [`-keep`](../configuration/usage.md#keep) option, e.g. "`-keep class com.example.MyClass`" or "`-keep class * implements com.example.MyInterface`". While setting up your configuration, you can specify the option [`-addconfigurationdebugging`](../configuration/usage.md#addconfigurationdebugging) to help track down these cases at run-time and let the instrumented code suggest settings for them. **NoSuchFieldException** {: #nosuchfieldexception} : Your code is probably calling something like `myClass.getField`, trying to find some field dynamically. Since ProGuard can't always detect this automatically, you have to keep the missing field using the appropriate [`-keep`](../configuration/usage.md#keep) option, e.g. "`-keepclassmembers class com.example.MyClass { int myField; }`". While setting up your configuration, you can specify the option [`-addconfigurationdebugging`](../configuration/usage.md#addconfigurationdebugging) to help track down these cases at run-time and let the instrumented code suggest settings for them. **NoSuchMethodException** {: #nosuchmethodexception} : Your code is probably calling something like `myClass.getMethod`, trying to find some method dynamically. Since ProGuard can't always detect this automatically, you have to keep the missing method using the appropriate [`-keep`](../configuration/usage.md#keep) option, e.g. "`-keepclassmembers class com.example.MyClass { void myMethod(); }`". While setting up your configuration, you can specify the option [`-addconfigurationdebugging`](../configuration/usage.md#addconfigurationdebugging) to help track down these cases at run-time and let the instrumented code suggest settings for them. More specifically, if the method reported as missing is `values` or `valueOf`, you probably have to keep some methods related to [enumerations](../configuration/examples.md#enumerations). **MissingResourceException** or **NullPointerException** : Your processed code may be unable to find some resource files. ProGuard simply copies resource files over from the input jars to the output jars. Their names and contents remain unchanged, unless you specify the options [`-adaptresourcefilenames`](../configuration/usage.md#adaptresourcefilenames) and/or [`-adaptresourcefilecontents`](../configuration/usage.md#adaptresourcefilecontents). Furthermore, directory entries in jar files aren't copied, unless you specify the option [`-keepdirectories`](../configuration/usage.md#keepdirectories). Note that Sun advises against calling `Class.getResource()` for directories (Sun Bug \#4761949](http://bugs.sun.com/view_bug.do?bug_id=4761949)). **Disappearing annotations** {: #disappearingannotations} : By default, the obfuscation step removes all annotations. If your application relies on annotations to function properly, you should explicitly keep them with `-keepattributes *Annotation*`. **Invalid or corrupt jarfile** {: #invalidjarfile} : You are probably starting your application with the java option `-jar` instead of the option `-classpath`. The java virtual machine returns with this error message if your jar doesn't contain a manifest file (`META-INF/MANIFEST.MF`), if the manifest file doesn't specify a main class (`Main-Class:` ...), or if the jar doesn't contain this main class. You should then make sure that the input jar contains a valid manifest file to start with, that this manifest file is the one that is copied (the first manifest file that is encountered), and that the main class is kept in your configuration, **InvalidJarIndexException: Invalid index** {: #invalidjarindexexception} : At least one of your processed jar files contains an index file `META-INF/INDEX.LIST`, listing all class files in the jar. ProGuard by default copies files like these unchanged. ProGuard may however remove or rename classes, thus invalidating the file. You should filter the index file out of the input (`-injars in.jar(!META-INF/INDEX.LIST)`) or update the file after having applied ProGuard (`jar -i out.jar`). **InvalidClassException**, **class loading error**, or **verification error** (in Java Micro Edition) : If you get such an error in Java Micro Edition, you may have forgotten to specify the [`-microedition`](../configuration/usage.md#microedition) option, so the processed class files are preverified properly. **Error: No Such Field or Method**, **Error verifying method** (in a Java Micro Edition emulator) : If you get such a message in a Motorola or Sony Ericsson phone emulator, it's because these emulators don't like packageless classes and/or overloaded fields and methods. You can work around it by not using the options `-repackageclasses ''` and [`-overloadaggressively`](../configuration/usage.md#overloadaggressively). **Failing midlets** (on a Java Micro Edition device) : If your midlet runs in an emulator and on some devices, but not on some other devices, this is probably due to a bug in the latter devices. For some older Motorola and Nokia phones, you might try specifying the [`-useuniqueclassmembernames`](../configuration/usage.md#useuniqueclassmembernames) option. It avoids overloading class member names, which triggers a bug in their java virtual machine. You might also try using the [`-dontusemixedcaseclassnames`](../configuration/usage.md#dontusemixedcaseclassnames) option. Even if the midlet has been properly processed and then preverified on a case-sensitive file system, the device itself might not like the mixed-case class names. Notably, the Nokia N-Gage emulator works fine, but the actual device seems to exhibit this problem. **Disappearing loops** {: #disappearingloops} : If your code contains empty busy-waiting loops, ProGuard's optimization step may remove them. More specifically, this happens if a loop continuously checks the value of a non-volatile field that is changed in a different thread. The specifications of the Java Virtual Machine require that you always mark fields that are accessed across different threads without further synchronization as `volatile`. If this is not possible for some reason, you'll have to switch off optimization using the [`-dontoptimize`](../configuration/usage.md#dontoptimize) option. **SecurityException: SHA1 digest error** {: #securityexception} : You may have forgotten to sign your program jar *after* having processed it with ProGuard. **ClassCastException: class not an enum**
**IllegalArgumentException: class not an enum type** {: #classcastexception} : You should make sure you're preserving the special methods of enumeration types, which the run-time environment calls by introspection. The required options are shown in the [examples](../configuration/examples.md#enumerations). **ArrayStoreException: sun.reflect.annotation.EnumConstantNotPresentExceptionProxy** {: #arraystoreexception} : You are probably processing annotations involving enumerations. Again, you should make sure you're preserving the special methods of the enumeration type, as shown in the examples. **IllegalArgumentException: methods with same signature but incompatible return types** {: #illegalargumentexception} : You are probably running some code that has been obfuscated with the [`-overloadaggressively`](../configuration/usage.md#overloadaggressively) option. The class `java.lang.reflect.Proxy` can't handle classes that contain methods with the same names and signatures, but different return types. Its method `newProxyInstance` then throws this exception. You can avoid the problem by not using the option. **CompilerError: duplicate addition** {: #compilererror} : You are probably compiling or running some code that has been obfuscated with the [`-overloadaggressively`](../configuration/usage.md#overloadaggressively) option. This option triggers a bug in `sun.tools.java.MethodSet.add` in Sun's JDK 1.2.2, which is used for (dynamic) compilation. You should then avoid this option. **ClassFormatError: repetitive field name/signature** {: #classformaterror1} : You are probably processing some code that has been obfuscated before with the [`-overloadaggressively`](../configuration/usage.md#overloadaggressively) option. You should then use the same option again in the second processing round. **ClassFormatError: Invalid index in LocalVariableTable in class file** {: #classformaterror2} : If you are keeping the `LocalVariableTable` or `LocalVariableTypeTable` attributes, ProGuard's optimizing step is sometimes unable to update them consistently. You should then let the obfuscation step remove these attributes or disable the optimization step. **NullPointerException: create returned null** (Dagger)
**IllegalStateException: Module adapter for class ... could not be loaded. Please ensure that code generation was run for this module.**
**IllegalStateException: Could not load class ... needed for binding members/...** {:#dagger} : Dagger 1 relies on reflection to combine annotated base classes and their corresponding generated classes. DexGuard's default configuration already preserves the generated classes, but you still preserve the annotated base classes in your project-specific configuration. This is explained in some more detail in the [Dagger example](../configuration/examples.md#dagger). **NoSuchMethodError** or **AbstractMethodError** {: #nosuchmethoderror} : You should make sure you're not writing your output class files to a directory on a platform with a case-insensitive file system, such as Windows. Please refer to the section about [disappearing classes](#disappearingclasses) for details. Furthermore, you should check whether you have specified your program jars and library jars properly. Program classes can refer to library classes, but not the other way around. If all of this seems ok, perhaps there's a bug in ProGuard (gasp!). If so, please report it, preferably with the simplest example on which you can find ProGuard to fail. **VerifyError** {: #verifyerror} : Verification errors when executing a program are almost certainly the result of a bug in the optimization step of ProGuard. Make sure you are using the latest version. You should be able to work around the problem by using the [`-dontoptimize`](../configuration/usage.md#dontoptimize) option. You can check the bug database to see if it is a known problem (often with a fix). Otherwise, please report it, preferably with the simplest example on which ProGuard fails. ================================================ FILE: docs/md/results.md ================================================ **ProGuard** successfully processes any Java bytecode, ranging from small applications to entire run-time libraries. It primarily reduces the size of the processed code, with some potential increase in efficiency as an added bonus. The improvements obviously depend on the original code. The table below presents some typical results: | Input Program | Original size | After shrinking | After optim. | After obfusc. | Total reduction | Time | Memory usage |-----------------------------------------------------------------------------------------------------------------|---------------|-----------------|--------------|---------------|-----------------|--------|-------------- | [Worm](http://www.oracle.com/technetwork/java/javame/index.html), a sample midlet from Oracle's JME | 10.3 K | 9.8 K | 9.6 K | 8.5 K | 18 % | 2 s | 19 M | [Javadocking](http://www.javadocking.com/), a docking library | 290 K | 281 K | 270 K | 201 K | 30 % | 12 s | 32 M | **ProGuard** itself | 648 K | 579 K | 557 K | 348 K | 46 % | 28 s | 66 M | [JDepend](http://www.clarkware.com/software/JDepend.html), a Java quality metrics tool | 57 K | 36 K | 33 K | 28 K | 51 % | 6 s | 24 M | [the run-time classes](http://www.oracle.com/technetwork/java/javase/overview/index.html) from Oracle's Java 6 | 53 M | 23 M | 22 M | 18 M | 66 % | 16 min | 270 M | [Tomcat](http://tomcat.apache.org/), the Apache servlet container | 1.1 M | 466 K | 426 K | 295 K | 74 % | 17 s | 44 M | [JavaNCSS](http://javancss.codehaus.org/), a Java source metrics tool | 632 K | 242 K | 212 K | 152 K | 75 % | 20 s | 36 M | [Ant](http://ant.apache.org/), the Apache build tool | 2.4 M | 401 K | 325 K | 242 K | 90 % | 23 s | 61 M Results were measured with ProGuard 4.0 on a 2.6 GHz Pentium 4 with 512 MB of memory, using Sun JDK 1.5.0 in Fedora Core 3 Linux. All of this technology and software has evolved since, but the gist of the results remains the same. The program sizes include companion libraries. The shrinking step produces the best results for programs that use only small parts of their libraries. The obfuscation step can significantly shrink large programs even further, since the identifiers of their many internal references can be replaced by short identifiers. The Java 6 run-time classes are the most complex example. The classes perform a lot of introspection, interacting with the native code of the virtual machine. The 1500+ lines of configuration were largely composed by automated analysis, complemented by a great deal of trial and error. The configuration is probably not complete, but the resulting library successfully serves as a run-time environment for running applications like ProGuard and the ProGuard GUI. For small inputs, timings are governed by the reading and parsing of the jars. For large inputs, the optimization step becomes more important. For instance, processing the Java 6 run-time classes without optimization only takes 2 minutes. Memory usage (the amount of physical memory used by ProGuard while processing) is governed by the basic java virtual machine and by the total size of the library jars and program jars. ================================================ FILE: docs/mkdocs.yml ================================================ ################################################################### # Project information ################################################################### site_name: ProGuard site_description: Optimizer and obfuscator for Java bytecode site_author: Guardsquare NV copyright: Copyright © 2002-2021 Guardsquare NV ################################################################### # Options ################################################################### theme: name: material logo: img/proguard.png favicon: img/guardsquare.png language: en palette: blue font: feature: docs_dir: md site_dir: html extra_css: - css/extra.css - css/admonition.css use_directory_urls: false #strict: true # broken links are errors ################################################################### # Theme specific ################################################################### extra: #font: # text: 'Droid Sans' # code: 'Ubuntu Mono' social: - icon: fontawesome/brands/github link: https://github.com/Guardsquare/proguard - icon: fontawesome/brands/twitter link: https://twitter.com/guardsquare - icon: fontawesome/brands/linkedin link: https://www.linkedin.com/company/guardsquare-nv - icon: fontawesome/brands/facebook link: https://www.facebook.com/guardsquare #feature: # tabs: true ################################################################### # Extensions ################################################################### markdown_extensions: - attr_list - admonition - footnotes - def_list - toc: permalink: True - codehilite: guess_lang: False - pymdownx.magiclink - pymdownx.tabbed - pymdownx.superfences: css_class: codehilite - pymdownx.striphtml: strip_comments: true plugins: - search ################################################################### # Page tree ################################################################### nav: - Home: manual/home.md - Quickstart: manual/quickstart.md - Building: manual/building.md - Setup: - Standalone: manual/setup/standalone.md - Android Gradle: manual/setup/gradleplugin.md - Java/Kotlin Gradle: manual/setup/gradle.md - Ant Task: manual/setup/ant.md - Configuration: - Usage: manual/configuration/usage.md - Attributes: manual/configuration/attributes.md - Optimizations: manual/configuration/optimizations.md - Examples: manual/configuration/examples.md - Languages: - Java: manual/languages/java.md - Kotlin: manual/languages/kotlin.md - Tools: - ReTrace: manual/tools/retrace.md - Playground: manual/tools/playground.md - AppSweep: manual/tools/appsweep.md - Troubleshooting: - Overview: manual/troubleshooting/troubleshooting.md - Limitations: manual/troubleshooting/limitations.md - Reference Card: manual/refcard.md - Version History: manual/releasenotes.md - FAQ: manual/FAQ.md - Feedback: manual/feedback.md - License: - Overview: manual/license/license.md - GPL: manual/license/gpl.md - GPL Exception: manual/license/gplexception.md ================================================ FILE: docs/notices/Gson ================================================ The ProGuard jar requires Gson (not included with ProGuard), which is licensed under the Apache License Version 2.0. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: dprotect/build.gradle ================================================ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { id 'com.github.johnrengelman.shadow' id 'java' id 'application' } repositories { mavenCentral() } dependencies { implementation project(':base') } jar.manifest.attributes('Implementation-Version': version) task fatJar(type: ShadowJar) { mainClassName = 'dprotect.DProtect' destinationDirectory.set(file("$rootDir/lib")) archiveFileName.set('dprotect.jar') configurations = [project.configurations.runtimeClasspath] manifest { attributes( 'Main-Class': 'dprotect.DProtect', 'Multi-Release': true, 'Implementation-Version': archiveVersion.get()) } } assemble.dependsOn fatJar ================================================ FILE: examples/android-agp3-agp4/AndroidManifest.xml ================================================ ================================================ FILE: examples/android-agp3-agp4/build.gradle ================================================ // This build file illustrates how to apply ProGuard in the Android build // process with AGP < 7, by swapping the built-in version of ProGuard for a newer version. // This process relies on setting `android.enableR8=false` in `gradle.properties`, // which is deprecated. For AGP7, please see the `android-plugin` example. buildscript { repositories { mavenLocal() // For local testing google() // For the Android plugin. mavenCentral() // For anything else. } dependencies { classpath 'com.android.tools.build:gradle:4.1.3' } configurations.all { resolutionStrategy { // Override the default version of ProGuard with the most recent one. dependencySubstitution { substitute module('net.sf.proguard:proguard-gradle') with module('com.guardsquare:proguard-gradle:7.3.0') } } } } apply plugin: 'com.android.application' android { compileSdkVersion 28 signingConfigs { debug { storeFile file('debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } defaultConfig { minSdkVersion 11 targetSdkVersion 28 signingConfig signingConfigs.debug } sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] resources.srcDirs = ['src'] aidl.srcDirs = ['src'] renderscript.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] } } buildTypes { debug { minifyEnabled false shrinkResources false } release { minifyEnabled true shrinkResources true proguardFile getDefaultProguardFile('proguard-android-optimize.txt') proguardFile 'proguard-project.txt' } } } repositories { google() // For the Android plugin. mavenCentral() // For anything else. } ================================================ FILE: examples/android-agp3-agp4/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: examples/android-agp3-agp4/gradle.properties ================================================ # Make sure that we use ProGuard instead of R8. android.enableR8=false android.enableR8.libraries=false ================================================ FILE: examples/android-agp3-agp4/gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # 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 # # https://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. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: examples/android-agp3-agp4/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: examples/android-agp3-agp4/proguard-project.txt ================================================ ############################################################################### # General settings. ############################################################################### -verbose # We can debug the ProGuard configuration by instrumenting the code and # checking the log for feedback. Disable the option again for actual releases! #-addconfigurationdebugging # We can also disable the individual processing steps. #-dontshrink #-dontoptimize #-dontobfuscate # Specifically target Android. -android ############################################################################### # Settings to handle reflection in the code. ############################################################################### # Preserve annotated and generated classes for Dagger. -keepclassmembers,allowobfuscation class * { @dagger.** *; } -keep class **$$ModuleAdapter -keep class **$$InjectAdapter -keep class **$$StaticInjection -if class **$$ModuleAdapter -keep class <1> -if class **$$InjectAdapter -keep class <1> -if class **$$StaticInjection -keep class <1> -keepnames class dagger.Lazy # Preserve annotated and generated classes for Butterknife. -keep class **$$ViewBinder { public static void bind(...); public static void unbind(...); } -if class **$$ViewBinder -keep class <1> -keep class **_ViewBinding { (<1>, android.view.View); } -if class **_ViewBinding -keep class <1> # Preserve fields that are serialized with GSON. #-keepclassmembers class com.example.SerializedClass1, # com.example.SerializedClass2 { # ; #} -keepclassmembers,allowobfuscation class * { @com.google.gson.annotations.SerializedName ; } -keep,allowobfuscation @interface com.google.gson.annotations.** ############################################################################### # Further optimizations. ############################################################################### # If you wish, you can let the optimization step remove Android logging calls. #-assumenosideeffects class android.util.Log { # public static boolean isLoggable(java.lang.String, int); # public static int v(...); # public static int i(...); # public static int w(...); # public static int d(...); # public static int e(...); #} # In that case, it's especially useful to also clean up any corresponding # string concatenation calls. -assumenoexternalsideeffects class java.lang.StringBuilder { public java.lang.StringBuilder(); public java.lang.StringBuilder(int); public java.lang.StringBuilder(java.lang.String); public java.lang.StringBuilder append(java.lang.Object); public java.lang.StringBuilder append(java.lang.String); public java.lang.StringBuilder append(java.lang.StringBuffer); public java.lang.StringBuilder append(char[]); public java.lang.StringBuilder append(char[], int, int); public java.lang.StringBuilder append(boolean); public java.lang.StringBuilder append(char); public java.lang.StringBuilder append(int); public java.lang.StringBuilder append(long); public java.lang.StringBuilder append(float); public java.lang.StringBuilder append(double); public java.lang.String toString(); } -assumenoexternalreturnvalues class java.lang.StringBuilder { public java.lang.StringBuilder append(java.lang.Object); public java.lang.StringBuilder append(java.lang.String); public java.lang.StringBuilder append(java.lang.StringBuffer); public java.lang.StringBuilder append(char[]); public java.lang.StringBuilder append(char[], int, int); public java.lang.StringBuilder append(boolean); public java.lang.StringBuilder append(char); public java.lang.StringBuilder append(int); public java.lang.StringBuilder append(long); public java.lang.StringBuilder append(float); public java.lang.StringBuilder append(double); } ================================================ FILE: examples/android-agp3-agp4/res/values/colors.xml ================================================ #4B7FCE #4B7FCE #7BAFCE #4B7FCE ================================================ FILE: examples/android-agp3-agp4/res/values/strings.xml ================================================ HelloWorld Sample ================================================ FILE: examples/android-agp3-agp4/res/values/styles.xml ================================================ ================================================ FILE: examples/android-agp3-agp4/settings.gradle ================================================ ================================================ FILE: examples/android-agp3-agp4/src/com/example/HelloWorldActivity.java ================================================ /* * Sample application to illustrate processing with ProGuard. * * Copyright (c) 2012-2020 Guardsquare NV */ package com.example; import android.app.Activity; import android.os.Bundle; import android.view.Gravity; import android.widget.TextView; /** * Sample activity that displays "Hello world!". */ public class HelloWorldActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Display the message. TextView view = new TextView(this); view.setText("Hello World"); view.setGravity(Gravity.CENTER); setContentView(view); } } ================================================ FILE: examples/android-plugin/AndroidManifest.xml ================================================ ================================================ FILE: examples/android-plugin/build.gradle ================================================ // This build file illustrates how to apply ProGuard in the Android build // process, with ProGuard's own plugin instead of the built-in minification // support of the Android Gradle plugin. buildscript { repositories { mavenLocal() // For local testing google() // For the Android Gradle plugin. mavenCentral() // For the ProGuard Gradle Plugin and anything else. } dependencies { classpath 'com.guardsquare:proguard-gradle:7.3.0' classpath 'com.android.tools.build:gradle:7.0.0' } } apply plugin: 'com.android.application' apply plugin: 'com.guardsquare.proguard' repositories { google() // For the Android plugin. mavenCentral() // For anything else. } android { compileSdkVersion 28 signingConfigs { debug { storeFile file('debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } defaultConfig { minSdkVersion 11 targetSdkVersion 29 signingConfig signingConfigs.debug } sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] resources.srcDirs = ['src'] aidl.srcDirs = ['src'] renderscript.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] } } buildTypes { debug { // Disable the built-in minification minifyEnabled false } release { // Disable the built-in minification minifyEnabled false } } } proguard { configurations { release { defaultConfiguration 'proguard-android-optimize.txt' configuration 'proguard-project.txt' } } } ================================================ FILE: examples/android-plugin/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: examples/android-plugin/gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # 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. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: examples/android-plugin/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem http://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: examples/android-plugin/proguard-project.txt ================================================ ############################################################################### # General settings. ############################################################################### -verbose # We can debug the ProGuard configuration by instrumenting the code and # checking the log for feedback. Disable the option again for actual releases! #-addconfigurationdebugging # We can also disable the individual processing steps. #-dontshrink #-dontoptimize #-dontobfuscate # Specifically target Android. -android ############################################################################### # Settings to handle reflection in the code. ############################################################################### # Preserve annotated and generated classes for Dagger. -keepclassmembers,allowobfuscation class * { @dagger.** *; } -keep class **$$ModuleAdapter -keep class **$$InjectAdapter -keep class **$$StaticInjection -if class **$$ModuleAdapter -keep class <1> -if class **$$InjectAdapter -keep class <1> -if class **$$StaticInjection -keep class <1> -keepnames class dagger.Lazy # Preserve annotated and generated classes for Butterknife. -keep class **$$ViewBinder { public static void bind(...); public static void unbind(...); } -if class **$$ViewBinder -keep class <1> -keep class **_ViewBinding { (<1>, android.view.View); } -if class **_ViewBinding -keep class <1> # Preserve fields that are serialized with GSON. #-keepclassmembers class com.example.SerializedClass1, # com.example.SerializedClass2 { # ; #} -keepclassmembers,allowobfuscation class * { @com.google.gson.annotations.SerializedName ; } -keep,allowobfuscation @interface com.google.gson.annotations.** ############################################################################### # Further optimizations. ############################################################################### # If you wish, you can let the optimization step remove Android logging calls. #-assumenosideeffects class android.util.Log { # public static boolean isLoggable(java.lang.String, int); # public static int v(...); # public static int i(...); # public static int w(...); # public static int d(...); # public static int e(...); #} # In that case, it's especially useful to also clean up any corresponding # string concatenation calls. -assumenoexternalsideeffects class java.lang.StringBuilder { public java.lang.StringBuilder(); public java.lang.StringBuilder(int); public java.lang.StringBuilder(java.lang.String); public java.lang.StringBuilder append(java.lang.Object); public java.lang.StringBuilder append(java.lang.String); public java.lang.StringBuilder append(java.lang.StringBuffer); public java.lang.StringBuilder append(char[]); public java.lang.StringBuilder append(char[], int, int); public java.lang.StringBuilder append(boolean); public java.lang.StringBuilder append(char); public java.lang.StringBuilder append(int); public java.lang.StringBuilder append(long); public java.lang.StringBuilder append(float); public java.lang.StringBuilder append(double); public java.lang.String toString(); } -assumenoexternalreturnvalues class java.lang.StringBuilder { public java.lang.StringBuilder append(java.lang.Object); public java.lang.StringBuilder append(java.lang.String); public java.lang.StringBuilder append(java.lang.StringBuffer); public java.lang.StringBuilder append(char[]); public java.lang.StringBuilder append(char[], int, int); public java.lang.StringBuilder append(boolean); public java.lang.StringBuilder append(char); public java.lang.StringBuilder append(int); public java.lang.StringBuilder append(long); public java.lang.StringBuilder append(float); public java.lang.StringBuilder append(double); } ================================================ FILE: examples/android-plugin/res/values/colors.xml ================================================ #4B7FCE #4B7FCE #7BAFCE #4B7FCE ================================================ FILE: examples/android-plugin/res/values/strings.xml ================================================ HelloWorld Sample ================================================ FILE: examples/android-plugin/res/values/styles.xml ================================================ ================================================ FILE: examples/android-plugin/settings.gradle ================================================ ================================================ FILE: examples/android-plugin/src/com/example/HelloWorldActivity.java ================================================ /* * Sample application to illustrate processing with ProGuard. * * Copyright (c) 2012-2020 Guardsquare NV */ package com.example; import android.app.Activity; import android.os.Bundle; import android.view.Gravity; import android.widget.TextView; /** * Sample activity that displays "Hello world!". */ public class HelloWorldActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Display the message. TextView view = new TextView(this); view.setText("Hello World"); view.setGravity(Gravity.CENTER); setContentView(view); } } ================================================ FILE: examples/annotations/examples/Applet.java ================================================ import proguard.annotation.*; /** * This applet illustrates the use of annotations for configuring ProGuard. * * You can compile it with: * javac -classpath ../lib/annotations.jar Applet.java * You can then process it with: * java -jar ../../../lib/proguard.jar @ ../examples.pro * * The annotation will preserve the class and its essential methods, * as a result of the specifications in lib/annotations.pro. */ @Keep public class Applet extends java.applet.Applet { // Implementations for Applet. public void init() { // ... } } ================================================ FILE: examples/annotations/examples/Application.java ================================================ import proguard.annotation.KeepApplication; /** * This application illustrates the use of annotations for configuring ProGuard. * * You can compile it with: * javac -classpath ../lib/annotations.jar Application.java * You can then process it with: * java -jar ../../../lib/proguard.jar @ ../examples.pro * * The annotation will preserve the class and its main method, * as a result of the specifications in lib/annotations.pro. */ @KeepApplication public class Application { public static void main(String[] args) { System.out.println("The answer is 42"); } } ================================================ FILE: examples/annotations/examples/Bean.java ================================================ import proguard.annotation.*; /** * This bean illustrates the use of annotations for configuring ProGuard. * * You can compile it with: * javac -classpath ../lib/annotations.jar Bean.java * You can then process it with: * java -jar ../../../lib/proguard.jar @ ../examples.pro * * The annotations will preserve the class and its public getters and setters, * as a result of the specifications in lib/annotations.pro. */ @Keep @KeepPublicGettersSetters public class Bean { public boolean booleanProperty; public int intProperty; public String stringProperty; public boolean isBooleanProperty() { return booleanProperty; } public void setBooleanProperty(boolean booleanProperty) { this.booleanProperty = booleanProperty; } public int getIntProperty() { return intProperty; } public void setIntProperty(int intProperty) { this.intProperty = intProperty; } public String getStringProperty() { return stringProperty; } public void setStringProperty(String stringProperty) { this.stringProperty = stringProperty; } } ================================================ FILE: examples/annotations/examples/NativeCallBack.java ================================================ import proguard.annotation.*; /** * This application illustrates the use of annotations for configuring ProGuard. * * You can compile it with: * javac -classpath ../lib/annotations.jar NativeCallBack.java * You can then process it with: * java -jar ../../../lib/proguard.jar @ ../examples.pro * * The annotation will preserve the class and its main method, * as a result of the specifications in lib/annotations.pro. */ @KeepApplication public class NativeCallBack { /** * Suppose this is a native method that computes an answer. * * The -keep option for native methods in the regular ProGuard * configuration will make sure it is not removed or renamed when * processing this code. */ public native int computeAnswer(); /** * Suppose this method is called back from the above native method. * * ProGuard would remove it, because it is not referenced from java. * The annotation will make sure it is preserved anyhow. */ @Keep public int getAnswer() { return 42; } /** * The main entry point of the application. * * The @KeepApplication annotation of this class will make sure it is not * removed or renamed when processing this code. */ public static void main(String[] args) { int answer = new NativeCallBack().computeAnswer(); System.out.println("The answer is " + answer); } } ================================================ FILE: examples/annotations/examples.pro ================================================ # # This ProGuard configuration file illustrates how to use annotations for # specifying which classes and class members should be kept. # Usage: # java -jar proguard.jar @examples.pro # # Specify the input, output, and library jars. # This is assuming the code has been compiled in the examples directory. -injars examples(*.class) -outjars out # Before Java 9, the runtime classes were packaged in a single jar file. #-libraryjars /lib/rt.jar # As of Java 9, the runtime classes are packaged in modular jmod files. -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) #-libraryjars /jmods/..... # Some important configuration is based on the annotations in the code. # We have to specify what the annotations mean to ProGuard. -include lib/annotations.pro # # We can then still add any other options that might be useful. # # Print out a list of what we're preserving. -printseeds # Preserve all annotations themselves. -keepattributes *Annotation* # Preserve all native method names and the names of their classes. -keepclasseswithmembernames,includedescriptorclasses class * { native ; } # Preserve the special static methods that are required in all enumeration # classes. -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Explicitly preserve all serialization members. The Serializable interface # is only a marker interface, so it wouldn't save them. # You can comment this out if your application doesn't use serialization. # If your code contains serializable classes that have to be backward # compatible, please refer to the manual. -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } ================================================ FILE: examples/ant/applets.xml ================================================ ================================================ FILE: examples/ant/applications1.xml ================================================ ================================================ FILE: examples/ant/applications2.xml ================================================ -verbose -injars in.jar -outjars out.jar -libraryjars ${java.home}/lib/rt.jar -printmapping out.map -renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable -keepattributes *Annotation* -keepclasseswithmembers public class * { public static void main(java.lang.String[]); } -keepclasseswithmembernames class * { native <methods>; } -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } ================================================ FILE: examples/ant/applications3.xml ================================================ ================================================ FILE: examples/ant/library.xml ================================================ ================================================ FILE: examples/ant/midlets.xml ================================================ ================================================ FILE: examples/ant/proguard.xml ================================================ ================================================ FILE: examples/ant/servlets.xml ================================================ ================================================ FILE: examples/application/build.gradle ================================================ import proguard.gradle.ProGuardTask buildscript { repositories { mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.3.0' } } plugins { id 'application' id 'java' } group = 'com.example' version = '0.0.1' application { mainClass.set('com.example.App') } repositories { mavenCentral() } dependencies { implementation 'com.google.guava:guava:30.1.1-jre' } ext.baseCoordinates = "${project.name}-${project.version}" tasks.register('proguard', ProGuardTask) { configuration file('proguard.pro') injars(tasks.named('jar', Jar).flatMap { it.archiveFile }) // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' //libraryjars "${System.getProperty('java.home')}/jmods/....." } verbose outjars(layout.buildDirectory.file("libs/${baseCoordinates}-minified.jar")) } ================================================ FILE: examples/application/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: examples/application/gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # 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 # # https://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. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: examples/application/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: examples/application/proguard.pro ================================================ # We only want minification, not obfuscation. -dontobfuscate -verbose # Entry point to the app. -keep class com.example.App { *; } ================================================ FILE: examples/application/settings.gradle ================================================ rootProject.name = 'demo' ================================================ FILE: examples/application/src/main/java/com/example/App.java ================================================ package com.example; public class App { public static void main(String... args) { System.out.println("Hello, world!"); } } ================================================ FILE: examples/application-kotlin/build.gradle ================================================ import proguard.gradle.ProGuardTask buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.3.1' } } plugins { id 'org.jetbrains.kotlin.jvm' version '1.8.0' id 'application' } group = 'org.example' version = '1.0-SNAPSHOT' repositories { mavenCentral() } dependencies { testImplementation 'org.jetbrains.kotlin:kotlin-test' } test { useJUnitPlatform() } compileKotlin { kotlinOptions.jvmTarget = '1.8' } compileTestKotlin { kotlinOptions.jvmTarget = '1.8' } application { mainClassName = 'AppKt' } ext.baseCoordinates = "${project.name}-${project.version}" tasks.register('proguard', ProGuardTask) { configuration file('proguard.pro') injars(tasks.named('jar', Jar).flatMap { it.archiveFile }) // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' //libraryjars "${System.getProperty('java.home')}/jmods/....." } // This will include the Kotlin library jars libraryjars sourceSets.main.compileClasspath verbose outjars(layout.buildDirectory.file("libs/${baseCoordinates}-minified.jar")) } ================================================ FILE: examples/application-kotlin/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: examples/application-kotlin/gradle.properties ================================================ kotlin.code.style=official ================================================ FILE: examples/application-kotlin/gradlew ================================================ #!/bin/sh # # Copyright © 2015-2021 the original authors. # # 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 # # https://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. # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in # double quotes to make sure that they get re-expanded; and # * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ org.gradle.wrapper.GradleWrapperMain \ "$@" # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: examples/application-kotlin/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: examples/application-kotlin/proguard.pro ================================================ -verbose -keepattributes *Annotation* -keep class kotlin.Metadata { *; } # Entry point to the app. -keep class com.example.AppKt { *; } ================================================ FILE: examples/application-kotlin/settings.gradle ================================================ rootProject.name = 'application-kotlin' ================================================ FILE: examples/application-kotlin/src/main/kotlin/App.kt ================================================ package com.example fun main(args: Array) { println("Hello World!") // Try adding program arguments via Run/Debug configuration. // Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html. println("Program arguments: ${args.joinToString()}") } ================================================ FILE: examples/dictionaries/compact.txt ================================================ # # This obfuscation dictionary contains strings that are already present # in many class files. Since these strings can be shared, the resulting # obfuscated class files will generally be a little bit more compact. # Usage: # java -jar proguard.jar ..... -obfuscationdictionary compact.txt # Code V I Z B C S F D L ================================================ FILE: examples/dictionaries/keywords.txt ================================================ # # This obfuscation dictionary contains reserved Java keywords. They can't # be used in Java source files, but they can be used in compiled class files. # Note that this hardly improves the obfuscation. Decent decompilers can # automatically replace reserved keywords, and the effect can fairly simply be # undone by obfuscating again with simpler names. # Usage: # java -jar proguard.jar ..... -obfuscationdictionary keywords.txt # do if for int new try byte case char else goto long this void break catch class const final float short super throw while double import native public return static switch throws boolean default extends finally package private abstract continue strictfp volatile interface protected transient implements instanceof synchronized ================================================ FILE: examples/dictionaries/shakespeare.txt ================================================ # # This obfuscation dictionary contains quotes from plays by Shakespeare. # It illustrates that any text can be used, for whatever flippant reasons # one may have. # Usage: # java -jar proguard.jar ..... -obfuscationdictionary shakespeare.txt # "This thing of darkness I acknowledge mine." --From The Tempest (V, i, 275-276) "Though this be madness, yet there is method in 't." --From Hamlet (II, ii, 206) "What's in a name? That which we call a rose By any other word would smell as sweet." --From Romeo and Juliet (II, ii, 1-2) ================================================ FILE: examples/dictionaries/windows.txt ================================================ # # This obfuscation dictionary contains names that are not allowed as file names # in Windows, not even with extensions like .class or .java. They can however # be used without problems in jar archives, which just begs to apply them as # obfuscated class names. Trying to unpack the obfuscated archives in Windows # will probably generate some sparks. # Usage: # java -jar proguard.jar ..... -classobfuscationdictionary windows.txt # -packageobfuscationdictionary windows.txt # aux Aux aUx AUx auX AuX aUX AUX AUX con Con cOn COn coN CoN cON CON CON nul Nul nUl NUl nuL NuL nUL NUL NUL prn Prn pRn PRn prN PrN pRN PRN PRN com1 Com1 cOm1 COm1 coM1 CoM1 cOM1 COM1 COM1 com2 Com2 cOm2 COm2 coM2 CoM2 cOM2 COM2 COM2 com3 Com3 cOm3 COm3 coM3 CoM3 cOM3 COM3 COM3 com4 Com4 cOm4 COm4 coM4 CoM4 cOM4 COM4 COM4 com5 Com5 cOm5 COm5 coM5 CoM5 cOM5 COM5 COM5 com6 Com6 cOm6 COm6 coM6 CoM6 cOM6 COM6 COM6 com7 Com7 cOm7 COm7 coM7 CoM7 cOM7 COM7 COM7 com8 Com8 cOm8 COm8 coM8 CoM8 cOM8 COM8 COM8 com9 Com9 cOm9 COm9 coM9 CoM9 cOM9 COM9 COM9 lpt1 Lpt1 lPt1 LPt1 lpT1 LpT1 lPT1 LPT1 LPT1 lpt2 Lpt2 lPt2 LPt2 lpT2 LpT2 lPT2 LPT2 LPT2 lpt3 Lpt3 lPt3 LPt3 lpT3 LpT3 lPT3 LPT3 LPT3 lpt4 Lpt4 lPt4 LPt4 lpT4 LpT4 lPT4 LPT4 LPT4 lpt5 Lpt5 lPt5 LPt5 lpT5 LpT5 lPT5 LPT5 LPT5 lpt6 Lpt6 lPt6 LPt6 lpT6 LpT6 lPT6 LPT6 LPT6 lpt7 Lpt7 lPt7 LPt7 lpT7 LpT7 lPT7 LPT7 LPT7 lpt8 Lpt8 lPt8 LPt8 lpT8 LpT8 lPT8 LPT8 LPT8 lpt9 Lpt9 lPt9 LPt9 lpT9 LpT9 lPT9 LPT9 LPT9 ================================================ FILE: examples/gradle/android.gradle ================================================ // // This Gradle build file illustrates how to process Android // applications. // // If you're using the Android SDK, the provided build systems with Gradle, // Android Studio, Eclipse, and Ant already take care of the proper settings. // You only need to enable ProGuard as explained in the Android documentation. // You can still add project-specific configuration in proguard-project.txt. // // This configuration file is for custom, stand-alone builds. // Usage: // gradle -b android.gradle proguard // Tell Gradle where to find the ProGuard task. buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.0.1' } } // Define a ProGuard task. task ('proguard', type: proguard.gradle.ProGuardTask) { // You should probably import a more compact ProGuard-style configuration // file for all static settings, but we're specifying them all here, for // the sake of the example. //configuration 'configuration.pro' //############################################################################## // Input and output. //############################################################################## // Specify the input jars, output jars, and library jars. // Note that ProGuard works with Java bytecode (.class), // before the dex compiler converts it into Dalvik code (.dex). injars 'classes' injars 'libs' outjars 'classes-processed.jar' libraryjars '/usr/local/android-sdk/platforms/android-27/android.jar' //libraryjars '/usr/local/java/android-sdk/extras/android/support/v4/android-support-v4.jar' //libraryjars '/usr/local/java/android-sdk/add-ons/addon-google_apis-google-21/libs/maps.jar' // ... // Save the obfuscation mapping to a file, so you can de-obfuscate any stack // traces later on. printmapping 'bin/classes-processed.map' // You can print out the seeds that are matching the keep options below. //printseeds 'bin/classes-processed.seeds' //############################################################################## // General settings. //############################################################################## verbose // We can debug the ProGuard configuration by instrumenting the code and // checking the log for feedback. Disable the option again for actual releases! //addconfigurationdebugging // We can also disable the individual processing steps. //dontshrink //dontoptimize //dontobfuscate // Specifically target Android. android // The dex compiler ignores preverification information. dontpreverify // Reduce the size of the output some more. repackageclasses '' allowaccessmodification // Switch off some optimizations that trip older versions of the Dalvik VM. optimizations '!code/simplification/arithmetic' // Keep a fixed source file attribute and all line number tables to get line // numbers in the stack traces. renamesourcefileattribute 'SourceFile' keepattributes 'SourceFile,LineNumberTable' //############################################################################## // Settings to handle reflection in the code. //############################################################################## // RemoteViews might need annotations. keepattributes '*Annotation*' // Preserve all fundamental application classes. keep 'public class * extends android.app.Activity' keep 'public class * extends android.app.Application' keep 'public class * extends android.app.Service' keep 'public class * extends android.content.BroadcastReceiver' keep 'public class * extends android.content.ContentProvider' // Preserve all View implementations, their special context constructors, and // their setters. keep 'public class * extends android.view.View { \ public (android.content.Context); \ public (android.content.Context, android.util.AttributeSet); \ public (android.content.Context, android.util.AttributeSet, int); \ public void set*(...); \ }' // Preserve all classes that have special context constructors, and the // constructors themselves. keepclasseswithmembers 'class * { \ public (android.content.Context, android.util.AttributeSet); \ }' // Preserve all classes that have special context constructors, and the // constructors themselves. keepclasseswithmembers 'class * { \ public (android.content.Context, android.util.AttributeSet, int); \ }' // Preserve all possible onClick handlers. keepclassmembers 'class * extends android.content.Context { \ public void *(android.view.View); \ public void *(android.view.MenuItem); \ }' // Preserve the special fields of all Parcelable implementations. keepclassmembers 'class * implements android.os.Parcelable { \ static android.os.Parcelable$Creator CREATOR; \ }' // Preserve static fields of inner classes of R classes that might be accessed // through introspection. keepclassmembers 'class **.R$* { \ public static ; \ }' // Preserve annotated Javascript interface methods. keepclassmembers 'class * { \ @android.webkit.JavascriptInterface ; \ }' // Preserve annotated and generated classes for Dagger. keepclassmembers allowobfuscation: true, 'class * { \ @dagger.** *; \ }' keep 'class **$$ModuleAdapter' keep 'class **$$InjectAdapter' keep 'class **$$StaticInjection' keep if: 'class **$$ModuleAdapter', 'class <1>' keep if: 'class **$$InjectAdapter', 'class <1>' keep if: 'class **$$StaticInjection', 'class <1>' keepnames 'class dagger.Lazy' // Preserve annotated and generated classes for Butterknife. keep 'class **$$ViewBinder { \ public static void bind(...); \ public static void unbind(...); \ }' keep if: 'class **$$ViewBinder', 'class <1>' keep 'class **_ViewBinding { \ (<1>, android.view.View); \ }' keep if: 'class **_ViewBinding', 'class <1>' // Preserve fields that are serialized with GSON. //keepclassmembers 'class com.example.SerializedClass1,' // com.example.SerializedClass2 { // ; //}' keepclassmembers allowobfuscation: true, 'class * { \ @com.google.gson.annotations.SerializedName ; \ }' keep allowobfuscation: true, '@interface com.google.gson.annotations.**' // Preserve the required interface from the License Verification Library // (but don't nag the developer if the library is not used at all). keep 'public interface com.android.vending.licensing.ILicensingService' dontnote 'com.android.vending.licensing.ILicensingService' // The Android Compatibility library references some classes that may not be // present in all versions of the API, but we know that's ok. dontwarn 'android.support.**' // Preserve all native method names and the names of their classes. keepclasseswithmembernames includedescriptorclasses: true, 'class * { \ native ; \ }' // Preserve the special static methods that are required in all enumeration // classes. keepclassmembers allowoptimization: true, 'enum * { \ public static **[] values(); \ public static ** valueOf(java.lang.String); \ }' // Explicitly preserve all serialization members. The Serializable interface // is only a marker interface, so it wouldn't save them. // You can comment this out if your application doesn't use serialization. // If your code contains serializable classes that have to be backward // compatible, please refer to the manual. keepclassmembers 'class * implements java.io.Serializable { \ static final long serialVersionUID; \ static final java.io.ObjectStreamField[] serialPersistentFields; \ private void writeObject(java.io.ObjectOutputStream); \ private void readObject(java.io.ObjectInputStream); \ java.lang.Object writeReplace(); \ java.lang.Object readResolve(); \ }' // Your application may contain more items that need to be preserved; // typically classes that are dynamically created using Class.forName: // keep 'public class com.example.MyClass' // keep 'public interface com.example.MyInterface' // keep 'public class * implements com.example.MyInterface' //############################################################################## // Further optimizations. //############################################################################## // If you wish, you can let the optimization step remove Android logging calls. assumenosideeffects 'class android.util.Log { \ public static boolean isLoggable(java.lang.String, int); \ public static int v(...); \ public static int i(...); \ public static int w(...); \ public static int d(...); \ public static int e(...); \ }' } ================================================ FILE: examples/gradle/applets.gradle ================================================ // // This Gradle build file illustrates how to process applets. // Usage: // gradle -b applets.gradle proguard // // Tell Gradle where to find the ProGuard task. buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.0.1' } } // Define a ProGuard task. task ('proguard', type: proguard.gradle.ProGuardTask) { // You should probably import a more compact ProGuard-style configuration // file for all static settings, but we're specifying them all here, for // the sake of the example. //configuration 'configuration.pro' verbose // Specify the input jars, output jars, and library jars. injars 'in.jar' outjars 'out.jar' // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod ", jarfilter: '!**.jar', filter: '!module-info.class' libraryjars "${System.getProperty('java.home')}/jmods/java.desktop.jmod", jarfilter: '!**.jar', filter: '!module-info.class' } // Save the obfuscation mapping to a file, so you can de-obfuscate any stack // traces later on. Keep a fixed source file attribute and all line number // tables to get line numbers in the stack traces. // You can comment this out if you're not interested in stack traces. printmapping 'out.map' renamesourcefileattribute 'SourceFile' keepattributes 'SourceFile,LineNumberTable' // Preserve all annotations. keepattributes '*Annotation*' // You can print out the seeds that are matching the keep options below. //printseeds 'out.seeds' // Preserve all public applets. keep 'public class * extends java.applet.Applet' // Preserve all native method names and the names of their classes. keepclasseswithmembernames,includedescriptorclasses 'class * { \ native ; \ }' // Preserve the special static methods that are required in all enumeration // classes. keepclassmembers allowoptimization: true, 'enum * { \ public static **[] values(); \ public static ** valueOf(java.lang.String); \ }' // Explicitly preserve all serialization members. The Serializable interface // is only a marker interface, so it wouldn't save them. // You can comment this out if your library doesn't use serialization. // If your code contains serializable classes that have to be backward // compatible, please refer to the manual. keepclassmembers 'class * implements java.io.Serializable { \ static final long serialVersionUID; \ static final java.io.ObjectStreamField[] serialPersistentFields; \ private void writeObject(java.io.ObjectOutputStream); \ private void readObject(java.io.ObjectInputStream); \ java.lang.Object writeReplace(); \ java.lang.Object readResolve(); \ }' // Your application may contain more items that need to be preserved; // typically classes that are dynamically created using Class.forName: // keep 'public class com.example.MyClass' // keep 'public interface com.example.MyInterface' // keep 'public class * implements com.example.MyInterface' } ================================================ FILE: examples/gradle/applications.gradle ================================================ // // This Gradle build file illustrates how to process applications. // Usage: // gradle -b applications.gradle proguard // // Tell Gradle where to find the ProGuard task. buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.0.1' } } // Define a ProGuard task. task ('proguard', type: proguard.gradle.ProGuardTask) { // You should probably import a more compact ProGuard-style configuration // file for all static settings, but we're specifying them all here, for // the sake of the example. //configuration 'configuration.pro' verbose // Specify the input jars, output jars, and library jars. injars 'in.jar' outjars 'out.jar' // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' //libraryjars "${System.getProperty('java.home')}/jmods/....." } //libraryjars 'junit.jar' //libraryjars 'servlet.jar' //libraryjars 'jai_core.jar' //... // Save the obfuscation mapping to a file, so you can de-obfuscate any stack // traces later on. Keep a fixed source file attribute and all line number // tables to get line numbers in the stack traces. // You can comment this out if you're not interested in stack traces. printmapping 'out.map' renamesourcefileattribute 'SourceFile' keepattributes 'SourceFile,LineNumberTable' // Preserve all annotations. keepattributes '*Annotation*' // You can print out the seeds that are matching the keep options below. //printseeds 'out.seeds' // Preserve all public applications. keepclasseswithmembers 'public class * { \ public static void main(java.lang.String[]); \ }' // Preserve all native method names and the names of their classes. keepclasseswithmembernames includedescriptorclasses: true, 'class * { \ native ; \ }' // Preserve the special static methods that are required in all enumeration // classes. keepclassmembers allowoptimization: true, 'enum * { \ public static **[] values(); \ public static ** valueOf(java.lang.String); \ }' // Explicitly preserve all serialization members. The Serializable interface // is only a marker interface, so it wouldn't save them. // You can comment this out if your application doesn't use serialization. // If your code contains serializable classes that have to be backward // compatible, please refer to the manual. keepclassmembers 'class * implements java.io.Serializable { \ static final long serialVersionUID; \ static final java.io.ObjectStreamField[] serialPersistentFields; \ private void writeObject(java.io.ObjectOutputStream); \ private void readObject(java.io.ObjectInputStream); \ java.lang.Object writeReplace(); \ java.lang.Object readResolve(); \ }' // Your application may contain more items that need to be preserved; // typically classes that are dynamically created using Class.forName: // keep 'public class com.example.MyClass' // keep 'public interface com.example.MyInterface' // keep 'public class * implements com.example.MyInterface' } ================================================ FILE: examples/gradle/library.gradle ================================================ // // This Gradle build file illustrates how to process a program // library, such that it remains usable as a library. // Usage: // gradle -b library.gradle proguard // // Tell Gradle where to find the ProGuard task. buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.0.1' } } // Define a ProGuard task. task ('proguard', type: proguard.gradle.ProGuardTask) { // You should probably import a more compact ProGuard-style configuration // file for all static settings, but we're specifying them all here, for // the sake of the example. //configuration 'configuration.pro' verbose // Specify the input jars, output jars, and library jars. // In this case, the input jar is the program library that we want to process. injars 'in.jar' outjars 'out.jar' // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' //libraryjars "${System.getProperty('java.home')}/jmods/....." } // Save the obfuscation mapping to a file, so we can de-obfuscate any stack // traces later on. Keep a fixed source file attribute and all line number // tables to get line numbers in the stack traces. // You can comment this out if you're not interested in stack traces. printmapping 'out.map' keepparameternames renamesourcefileattribute 'SourceFile' keepattributes 'Signature,Exceptions,InnerClasses,PermittedSubclasses,EnclosingMethod,Deprecated,SourceFile,LineNumberTable' // Preserve all annotations. keepattributes '*Annotation*' // Preserve all public classes, and their public and protected fields and // methods. keep 'public class * { \ public protected *; \ }' // Preserve all .class method names. keepclassmembernames 'class * { \ java.lang.Class class$(java.lang.String); \ java.lang.Class class$(java.lang.String, boolean); \ }' // Preserve all native method names and the names of their classes. keepclasseswithmembernames includedescriptorclasses: true, 'class * { \ native ; \ }' // Preserve the special static methods that are required in all enumeration // classes. keepclassmembers allowoptimization: true, 'enum * { \ public static **[] values(); \ public static ** valueOf(java.lang.String); \ }' // Explicitly preserve all serialization members. The Serializable interface // is only a marker interface, so it wouldn't save them. // You can comment this out if your library doesn't use serialization. // If your code contains serializable classes that have to be backward // compatible, please refer to the manual. keepclassmembers 'class * implements java.io.Serializable { \ static final long serialVersionUID; \ static final java.io.ObjectStreamField[] serialPersistentFields; \ private void writeObject(java.io.ObjectOutputStream); \ private void readObject(java.io.ObjectInputStream); \ java.lang.Object writeReplace(); \ java.lang.Object readResolve(); \ }' // Your library may contain more items that need to be preserved; // typically classes that are dynamically created using Class.forName: // keep 'public class com.example.MyClass' // keep 'public interface com.example.MyInterface' // keep 'public class * implements com.example.MyInterface' } ================================================ FILE: examples/gradle/midlets.gradle ================================================ // // This Gradle build file illustrates how to process J2ME midlets. // Usage: // gradle -b midlets.gradle proguard // // Tell Gradle where to find the ProGuard task. buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.0.1' } } // Define a ProGuard task. task ('proguard', type: proguard.gradle.ProGuardTask) { // You should probably import a more compact ProGuard-style configuration // file for all static settings, but we're specifying them all here, for // the sake of the example. //configuration 'configuration.pro' verbose // Specify the input jars, output jars, and library jars. injars 'in.jar' outjars 'out.jar' libraryjars '/usr/local/java/wtk2.5.2/lib/midpapi20.jar' libraryjars '/usr/local/java/wtk2.5.2/lib/cldcapi11.jar' // Preverify the code suitably for Java Micro Edition. microedition // Allow methods with the same signature, except for the return type, // to get the same obfuscation name. overloadaggressively // Put all obfuscated classes into the nameless root package. repackageclasses '' // Allow classes and class members to be made public. allowaccessmodification // On Windows, you can't use mixed case class names, // should you still want to use the preverify tool. // // dontusemixedcaseclassnames // Save the obfuscation mapping to a file, so you can de-obfuscate any stack // traces later on. printmapping 'out.map' // You can keep a fixed source file attribute and all line number tables to // get stack traces with line numbers. //renamesourcefileattribute 'SourceFile' //keepattributes 'SourceFile,LineNumberTable' // You can print out the seeds that are matching the keep options below. //printseeds 'out.seeds' // Preserve all public midlets. keep 'public class * extends javax.microedition.midlet.MIDlet' // Preserve all native method names and the names of their classes. keepclasseswithmembernames includedescriptorclasses: true, 'class * { \ native ; \ }' // Your midlet may contain more items that need to be preserved; // typically classes that are dynamically created using Class.forName: // keep 'public class com.example.MyClass' // keep 'public interface com.example.MyInterface' // keep 'public class * implements com.example.MyInterface' } ================================================ FILE: examples/gradle/proguard.gradle ================================================ // // This Gradle build file illustrates how to process ProGuard itself. // Configuration files for typical applications will be very similar. // Usage: // gradle -b proguard.gradle proguard // // Tell Gradle where to find the ProGuard task. buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.0.1' } } // Define a ProGuard task. task ('proguard', type: proguard.gradle.ProGuardTask) { // You should probably import a more compact ProGuard-style configuration // file for all static settings, but we're specifying them all here, for // the sake of the example. //configuration 'configuration.pro' verbose // Specify the input jars, output jars, and library jars. injars '../../lib/proguard.jar' outjars 'proguard_out.jar' // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' libraryjars "${System.getProperty('java.home')}/jmods/java.sql.jmod", jarfilter: '!**.jar', filter: '!module-info.class' //libraryjars "${System.getProperty('java.home')}/jmods/....." } // Write out an obfuscation mapping file, for de-obfuscating any stack traces // later on, or for incremental obfuscation of extensions. printmapping 'proguard.map' // Don't print notes about reflection in GSON code, the Kotlin runtime, and // our own optionally injected code. dontnote 'kotlin.**' dontnote 'kotlinx.**' dontnote 'com.google.gson.**' dontnote 'proguard.configuration.ConfigurationLogger' // Preserve injected GSON utility classes and their members. keep allowobfuscation: true, 'class proguard.optimize.gson._*' keepclassmembers 'class proguard.optimize.gson._* { \ *; \ }' // Obfuscate class strings of injected GSON utility classes. adaptclassstrings 'proguard.optimize.gson.**' // Allow methods with the same signature, except for the return type, // to get the same obfuscation name. overloadaggressively // Put all obfuscated classes into the nameless root package. repackageclasses '' // Allow classes and class members to be made public. allowaccessmodification // The entry point: ProGuard and its main method. keep 'public class proguard.ProGuard { \ public static void main(java.lang.String[]); \ }' // If you want to preserve the Ant task as well, you'll have to specify the // main ant.jar. //libraryjars '/usr/local/java/ant/lib/ant.jar' //adaptresourcefilecontents 'proguard/ant/task.properties' // //keep allowobfuscation: true, 'class proguard.ant.*' //keepclassmembers 'public class proguard.ant.* { \ // (org.apache.tools.ant.Project); \ // public void set*(***); \ // public void add*(***); \ //}' // If you want to preserve the Gradle task, you'll have to specify the Gradle // jars. //libraryjars '/usr/local/java/gradle-4.2.1/lib/plugins/gradle-plugins-4.2.1.jar' //libraryjars '/usr/local/java/gradle-4.2.1/lib/gradle-base-services-4.2.1.jar' //libraryjars '/usr/local/java/gradle-4.2.1/lib/gradle-core-4.2.1.jar' //libraryjars '/usr/local/java/gradle-4.2.1/lib/groovy-all-2.4.12.jar' //keep 'public class proguard.gradle.* { \ // public *; \ //}' // If you want to preserve the WTK obfuscation plug-in, you'll have to specify // the kenv.zip file. //libraryjars '/usr/local/java/wtk2.5.2/wtklib/kenv.zip' //keep 'public class proguard.wtk.ProGuardObfuscator' } ================================================ FILE: examples/gradle/proguardgui.gradle ================================================ // // This Gradle build file illustrates how to process the ProGuard GUI. // Configuration files for typical applications will be very similar. // Usage: // gradle -b proguardgui.gradle proguard // // Tell Gradle where to find the ProGuard task. buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.0.1' } } // Define a ProGuard task. task ('proguard', type: proguard.gradle.ProGuardTask) { // You should probably import a more compact ProGuard-style configuration // file for all static settings, but we're specifying them all here, for // the sake of the example. //configuration 'configuration.pro' verbose // Specify the input jars, output jars, and library jars. // The input jars will be merged in a single output jar. injars '../../lib/proguardgui.jar' outjars 'proguardgui_out.jar' // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' libraryjars "${System.getProperty('java.home')}/jmods/java.sql.jmod", jarfilter: '!**.jar', filter: '!module-info.class' libraryjars "${System.getProperty('java.home')}/jmods/java.desktop.jmod", jarfilter: '!**.jar', filter: '!module-info.class' //libraryjars "${System.getProperty('java.home')}/jmods/....." } // Write out an obfuscation mapping file, for de-obfuscating any stack traces // later on, or for incremental obfuscation of extensions. printmapping 'proguardgui.map' // If we wanted to reuse the previously obfuscated proguard_out.jar, we could // perform incremental obfuscation based on its mapping file, and only keep the // additional GUI files instead of all files. //applymapping 'proguard.map' //injars '../../lib/proguardgui.jar' //outjars 'proguardgui_out.jar' //libraryjars '../../lib/proguard.jar', filter: '!proguard/ant/**,!proguard/wtk/**' //libraryjars '../../lib/retrace.jar' //libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' //libraryjars "${System.getProperty('java.home')}/jmods/java.desktop.jmod", jarfilter: '!**.jar', filter: '!module-info.class' // Don't print notes about reflection in GSON code, the Kotlin runtime, and // our own optionally injected code. dontnote 'kotlin.**' dontnote 'kotlinx.**' dontnote 'com.google.gson.**' dontnote 'proguard.configuration.ConfigurationLogger' // Allow methods with the same signature, except for the return type, // to get the same obfuscation name. overloadaggressively // Put all obfuscated classes into the nameless root package. repackageclasses '' // Adapt the names of resource files, based on the corresponding obfuscated // class names. Notably, in this case, the GUI resource properties file will // have to be renamed. adaptresourcefilenames '**.properties,**.gif,**.jpg' // The entry point: ProGuardGUI and its main method. keep 'public class proguard.gui.ProGuardGUI { \ public static void main(java.lang.String[]); \ }' } ================================================ FILE: examples/gradle/retrace.gradle ================================================ // // This Gradle build file illustrates how to process the ReTrace tool. // Configuration files for typical applications will be very similar. // Usage: // gradle -b retrace.gradle proguard // // Tell Gradle where to find the ProGuard task. buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.0.1' } } // Define a ProGuard task. task ('proguard', type: proguard.gradle.ProGuardTask) { // You should probably import a more compact ProGuard-style configuration // file for all static settings, but we're specifying them all here, for // the sake of the example. //configuration 'configuration.pro' verbose // Specify the input jars, output jars, and library jars. // The input jars will be merged in a single output jar. injars '../../lib/retrace.jar' outjars 'retrace_out.jar' // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' libraryjars "${System.getProperty('java.home')}/jmods/java.sql.jmod", jarfilter: '!**.jar', filter: '!module-info.class' //libraryjars "${System.getProperty('java.home')}/jmods/....." } // Write out an obfuscation mapping file, for de-obfuscating any stack traces // later on, or for incremental obfuscation of extensions. printmapping 'retrace.map' // If we wanted to reuse the previously obfuscated proguard_out.jar, we could // perform incremental obfuscation based on its mapping file, and only keep the // additional ReTrace files instead of all files. //applymapping 'proguard.map' //outjars 'retrace_out.jar', filter: 'proguard/retrace/**' // Don't print notes about reflection in GSON code, the Kotlin runtime, and // our own optionally injected code. dontnote 'kotlin.**' dontnote 'kotlinx.**' dontnote 'com.google.gson.**' dontnote 'proguard.configuration.ConfigurationLogger' // Allow methods with the same signature, except for the return type, // to get the same obfuscation name. overloadaggressively // Put all obfuscated classes into the nameless root package. repackageclasses '' // Allow classes and class members to be made public. allowaccessmodification // The entry point: ReTrace and its main method. keep 'public class proguard.retrace.ReTrace { \ public static void main(java.lang.String[]); \ }' } ================================================ FILE: examples/gradle/scala.gradle ================================================ // // This Gradle build file illustrates how to process Scala // applications, including the Scala runtime. // Usage: // gradle -b scala.gradle proguard // // Tell Gradle where to find the ProGuard task. buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.0.1' } } // Define a ProGuard task. task ('proguard', type: proguard.gradle.ProGuardTask) { // You should probably import a more compact ProGuard-style configuration // file for all static settings, but we're specifying them all here, for // the sake of the example. //configuration 'configuration.pro' verbose // Specify the input jars, output jars, and library jars. injars 'in.jar' injars '/usr/local/java/scala-2.9.1/lib/scala-library.jar' //injars '/usr/local/java/scala-2.9.1/lib/scala-compiler.jar', filter: '!META-INF/MANIFEST.MF' //injars '/usr/local/java/scala-2.9.1/lib/jline.jar', filter: '!META-INF/MANIFEST.MF' outjars 'out.jar' // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' //libraryjars "${System.getProperty('java.home')}/jmods/....." } //libraryjars '/usr/local/java/ant/lib/ant.jar' //... // Ignore some compiler artefacts. dontwarn 'scala.**' // Save the obfuscation mapping to a file, so you can de-obfuscate any stack // traces later on. Keep a fixed source file attribute and all line number // tables to get line numbers in the stack traces. // You can comment this out if you're not interested in stack traces. printmapping 'out.map' renamesourcefileattribute 'SourceFile' keepattributes 'SourceFile,LineNumberTable' // Preserve all annotations. keepattributes '*Annotation*' // You can print out the seeds that are matching the keep options below. //printseeds 'out.seeds' // Preserve all public applications. keepclasseswithmembers 'public class * { \ public static void main(java.lang.String[]); \ }' // Preserve some classes and class members that are accessed by means of // introspection. keep 'class * implements org.xml.sax.EntityResolver' keepclassmembers 'class * { \ ** MODULE$; \ }' keepclassmembernames 'class scala.concurrent.forkjoin.ForkJoinPool { \ long eventCount; \ int workerCounts; \ int runControl; \ scala.concurrent.forkjoin.ForkJoinPool$WaitQueueNode syncStack; \ scala.concurrent.forkjoin.ForkJoinPool$WaitQueueNode spareStack; \ }' keepclassmembernames 'class scala.concurrent.forkjoin.ForkJoinWorkerThread { \ int base; \ int sp; \ int runState; \ }' keepclassmembernames 'class scala.concurrent.forkjoin.ForkJoinTask { \ int status; \ }' keepclassmembernames 'class scala.concurrent.forkjoin.LinkedTransferQueue { \ scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference head; \ scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference tail; \ scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference cleanMe; \ }' // Preserve some classes and class members that are accessed by means of // introspection in the Scala compiler library, if it is processed as well. //keep 'class * implements jline.Completor' //keep 'class * implements jline.Terminal' //keep 'class scala.tools.nsc.Global' //keepclasseswithmembers 'class * { \ // (scala.tools.nsc.Global); \ //}' //keepclassmembers 'class * { \ // *** scala_repl_value(); \ // *** scala_repl_result(); \ //}' // Preserve all native method names and the names of their classes. keepclasseswithmembernames includedescriptorclasses: true, 'class * { \ native ; \ }' // Preserve the special static methods that are required in all enumeration // classes. keepclassmembers allowoptimization: true, 'enum * { \ public static **[] values(); \ public static ** valueOf(java.lang.String); \ }' // Explicitly preserve all serialization members. The Serializable interface // is only a marker interface, so it wouldn't save them. // You can comment this out if your application doesn't use serialization. // If your code contains serializable classes that have to be backward // compatible, please refer to the manual. keepclassmembers 'class * implements java.io.Serializable { \ static final long serialVersionUID; \ static final java.io.ObjectStreamField[] serialPersistentFields; \ private void writeObject(java.io.ObjectOutputStream); \ private void readObject(java.io.ObjectInputStream); \ java.lang.Object writeReplace(); \ java.lang.Object readResolve(); \ }' // Your application may contain more items that need to be preserved; // typically classes that are dynamically created using Class.forName: // keep 'public class com.example.MyClass' // keep 'public interface com.example.MyInterface' // keep 'public class * implements com.example.MyInterface' } ================================================ FILE: examples/gradle/servlets.gradle ================================================ // // This Gradle build file illustrates how to process servlets. // Usage: // gradle -b servlets.gradle proguard // // Tell Gradle where to find the ProGuard task. buildscript { repositories { mavenLocal() mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.0.1' } } // Define a ProGuard task. task ('proguard', type: proguard.gradle.ProGuardTask) { // You should probably import a more compact ProGuard-style configuration // file for all static settings, but we're specifying them all here, for // the sake of the example. //configuration 'configuration.pro' verbose // Specify the input jars, output jars, and library jars. injars 'in.jar' outjars 'out.jar' // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' } libraryjars '/usr/local/java/servlet/servlet.jar' // Save the obfuscation mapping to a file, so you can de-obfuscate any stack // traces later on. Keep a fixed source file attribute and all line number // tables to get line numbers in the stack traces. // You can comment this out if you're not interested in stack traces. printmapping 'out.map' renamesourcefileattribute 'SourceFile' keepattributes 'SourceFile,LineNumberTable' // Preserve all annotations. keepattributes '*Annotation*' // You can print out the seeds that are matching the keep options below. //printseeds 'out.seeds' // Preserve all public servlets. keep 'public class * implements javax.servlet.Servlet' // Preserve all native method names and the names of their classes. keepclasseswithmembernames includedescriptorclasses: true, 'class * { \ native ; \ }' // Preserve the special static methods that are required in all enumeration // classes. keepclassmembers allowoptimization: true, 'enum * { \ public static **[] values(); \ public static ** valueOf(java.lang.String); \ }' // Explicitly preserve all serialization members. The Serializable interface // is only a marker interface, so it wouldn't save them. // You can comment this out if your library doesn't use serialization. // If your code contains serializable classes that have to be backward // compatible, please refer to the manual. keepclassmembers 'class * implements java.io.Serializable { \ static final long serialVersionUID; \ static final java.io.ObjectStreamField[] serialPersistentFields; \ private void writeObject(java.io.ObjectOutputStream); \ private void readObject(java.io.ObjectInputStream); \ java.lang.Object writeReplace(); \ java.lang.Object readResolve(); \ }' // Your application may contain more items that need to be preserved; // typically classes that are dynamically created using Class.forName: // keep 'public class com.example.MyClass' // keep 'public interface com.example.MyInterface' // keep 'public class * implements com.example.MyInterface' } ================================================ FILE: examples/gradle/settings.gradle ================================================ // // Gradle Settings file, required to test any of the example gradle build files in this directory. // Invoke Gradle with the `-b` argument to point at a particular build file // rootProject.name = "proguard-gradle-examples" ================================================ FILE: examples/gradle-kotlin-dsl/.gitattributes ================================================ # # https://help.github.com/articles/dealing-with-line-endings/ # # These are explicitly windows files and should use crlf *.bat text eol=crlf ================================================ FILE: examples/gradle-kotlin-dsl/.gitignore ================================================ # Ignore Gradle project-specific cache directory .gradle # Ignore Gradle build output directory build ================================================ FILE: examples/gradle-kotlin-dsl/build.gradle.kts ================================================ buildscript { repositories { mavenCentral() google() } dependencies { classpath("com.guardsquare:proguard-gradle:7.3.0") } } plugins { java application } repositories { mavenCentral() } dependencies { testImplementation("junit:junit:4.12") } application { mainClassName = "gradlekotlindsl.App" } tasks.withType { manifest { attributes["Main-Class"] = application.mainClassName } } tasks.register("proguard") { verbose() // Alternatively put your config in a separate file // configuration("config.pro") // Use the jar task output as a input jar. This will automatically add the necessary task dependency. injars(tasks.named("jar")) outjars("build/proguard-obfuscated.jar") val javaHome = System.getProperty("java.home") // Automatically handle the Java version of this build. if (System.getProperty("java.version").startsWith("1.")) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars("$javaHome/lib/rt.jar") } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars( // filters must be specified first, as a map mapOf("jarfilter" to "!**.jar", "filter" to "!module-info.class"), "$javaHome/jmods/java.base.jmod" ) } allowaccessmodification() repackageclasses("") printmapping("build/proguard-mapping.txt") keep("""class gradlekotlindsl.App { public static void main(java.lang.String[]); } """) } ================================================ FILE: examples/gradle-kotlin-dsl/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: examples/gradle-kotlin-dsl/gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # 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 # # https://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. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: examples/gradle-kotlin-dsl/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: examples/gradle-kotlin-dsl/settings.gradle.kts ================================================ rootProject.name = "gradle-kotlin-dsl" ================================================ FILE: examples/gradle-kotlin-dsl/src/main/java/gradlekotlindsl/App.java ================================================ /* * This Java source file was generated by the Gradle 'init' task. */ package gradlekotlindsl; public class App { public String getGreeting() { return "Hello world."; } public static void main(String[] args) { System.out.println(new App().getGreeting()); } } ================================================ FILE: examples/gradle-kotlin-dsl/src/test/java/gradlekotlindsl/AppTest.java ================================================ /* * This Java source file was generated by the Gradle 'init' task. */ package gradlekotlindsl; import org.junit.Test; import static org.junit.Assert.*; public class AppTest { @Test public void testAppHasAGreeting() { App classUnderTest = new App(); assertNotNull("app should have a greeting", classUnderTest.getGreeting()); } } ================================================ FILE: examples/spring-boot/.gitignore ================================================ HELP.md .gradle build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache bin/ !**/src/main/**/bin/ !**/src/test/**/bin/ ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ !**/src/main/**/out/ !**/src/test/**/out/ ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ ### VS Code ### .vscode/ ================================================ FILE: examples/spring-boot/README.md ================================================ # Demo Application demonstrating ProGuard applied to a Spring Boot application ## Building ``` ./gradlew clean proguard --info ``` Spring Boot applications contain a BOOT-INF folder which contains the application class files and library jars. We must first extract the program classes, then apply ProGuard to them and finally repackage the application. ## Executing The unobfuscated application will be located at `build/libs/demo-0.0.1.jar` and the obfuscated application will be located at `build/libs/demo-0.0.1-obfuscated.jar`. They can be executed as follows: ``` java -jar build/libs/demo-0.0.1.jar ``` or ``` java -jar build/libs/demo-0.0.1-obfuscated.jar ``` ================================================ FILE: examples/spring-boot/build.gradle ================================================ import proguard.gradle.ProGuardTask buildscript { repositories { mavenCentral() google() } dependencies { classpath 'com.guardsquare:proguard-gradle:7.3.0' } } plugins { id 'org.springframework.boot' version '2.3.5.RELEASE' id 'io.spring.dependency-management' version '1.0.10.RELEASE' id 'java' } group = 'com.example' version = '0.0.1' sourceCompatibility = '1.8' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter' } task extractJar(type: Copy) { dependsOn tasks.assemble def zipFile = file("${buildDir}/libs/demo-${version}.jar") def outputDir = file("${buildDir}/extracted/") from zipTree(zipFile) into outputDir } task deleteClasses(type: Delete) { delete "${buildDir}/extracted/BOOT-INF/classes/" } task copyObfuscatedClasses(type: Copy) { dependsOn tasks.deleteClasses from zipTree("${buildDir}/obfuscatedClasses.jar") into "${buildDir}/extracted/BOOT-INF/classes/" } task deleteObfuscated(type: Delete) { delete 'build/obfuscatedClasses.jar' } task repackage(type: Zip) { dependsOn tasks.deleteClasses dependsOn tasks.copyObfuscatedClasses dependsOn tasks.deleteObfuscated from "${buildDir}/extracted" entryCompression ZipEntryCompression.STORED archiveFileName= "demo-${archiveVersion.get()}-obfuscated.jar" destinationDirectory = file("${buildDir}/libs") } task proguard(type: ProGuardTask) { dependsOn tasks.extractJar verbose injars "${buildDir}/extracted/BOOT-INF/classes" outjars "${buildDir}/obfuscatedClasses.jar" // Automatically handle the Java version of this build. if (System.getProperty('java.version').startsWith('1.')) { // Before Java 9, the runtime classes were packaged in a single jar file. libraryjars "${System.getProperty('java.home')}/lib/rt.jar" } else { // As of Java 9, the runtime classes are packaged in modular jmod files. libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class' //libraryjars "${System.getProperty('java.home')}/jmods/....." } // This will contain the Spring dependencies. libraryjars sourceSets.main.compileClasspath keepdirectories // Keep the main class entry point. keep 'public class com.example.demo.DemoApplication { \ public static void main(java.lang.String[]); \ }' keepattributes '*Annotation*' // This simple example requires classes with @Component annotation classes // to be kept, since otherwise components could end up with clashing names, // if they do not set the name explicitly. keep 'public @org.springframework.stereotype.Component class *' // You may need to keep classes or members based on other annotations such as: keepclassmembers 'public class * { \ @org.springframework.beans.factory.annotation.Autowired *; \ @org.springframework.beans.factory.annotation.Value *; \ }' // After ProGuard has executed, repackage the app. finalizedBy tasks.repackage } ================================================ FILE: examples/spring-boot/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: examples/spring-boot/gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # 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 # # https://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. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: examples/spring-boot/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: examples/spring-boot/settings.gradle ================================================ rootProject.name = 'demo' ================================================ FILE: examples/spring-boot/src/main/java/com/example/demo/DemoApplication.java ================================================ package com.example.demo; import com.example.demo.sub.TestComponent2; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.scan("com.example.demo"); context.refresh(); TestComponent ms = context.getBean(TestComponent.class); TestComponent2 ms2 = context.getBean(TestComponent2.class); System.out.println("The answer is " + ms.getAnswer() + " " + ms2.getMessage()); context.close(); } } ================================================ FILE: examples/spring-boot/src/main/java/com/example/demo/TestComponent.java ================================================ package com.example.demo; import org.springframework.stereotype.Component; /** * The component name will be "TestComponent" since it is * not set explicitly like @Component("MyComponent"). */ @Component public class TestComponent { public int getAnswer() { return 42; } } ================================================ FILE: examples/spring-boot/src/main/java/com/example/demo/sub/TestComponent2.java ================================================ package com.example.demo.sub; import org.springframework.stereotype.Component; /** * The component name will be "TestComponent2" since it is * not set explicitly like @Component("MyComponent2"). */ @Component public class TestComponent2 { public String getMessage() { return "hello"; } } ================================================ FILE: examples/spring-boot/src/main/resources/application.properties ================================================ ================================================ FILE: examples/standalone/android.pro ================================================ # # This ProGuard configuration file illustrates how to process Android # applications. # # If you're using the Android SDK, the provided build systems with Gradle, # Android Studio, Eclipse, and Ant already take care of the proper settings. # You only need to enable ProGuard as explained in the Android documentation. # You can still add project-specific configuration in proguard-project.txt. # # This configuration file is for custom, stand-alone builds. # Usage: # java -jar proguard.jar @android.pro ############################################################################### # Input and output. ############################################################################### # Specify the input jars, output jars, and library jars. # Note that ProGuard works with Java bytecode (.class), # before the dex compiler converts it into Dalvik code (.dex). -injars bin/classes -injars libs -outjars bin/classes-processed.jar -libraryjars /usr/local/android-sdk/platforms/android-27/android.jar #-libraryjars /usr/local/java/android-sdk/extras/android/support/v4/android-support-v4.jar #-libraryjars /usr/local/java/android-sdk/add-ons/addon-google_apis-google-21/libs/maps.jar # ... # Save the obfuscation mapping to a file, so you can de-obfuscate any stack # traces later on. -printmapping bin/classes-processed.map # You can print out the seeds that are matching the keep options below. #-printseeds bin/classes-processed.seeds ############################################################################### # General settings. ############################################################################### -verbose # We can debug the ProGuard configuration by instrumenting the code and # checking the log for feedback. Disable the option again for actual releases! #-addconfigurationdebugging # We can also disable the individual processing steps. #-dontshrink #-dontoptimize #-dontobfuscate # Specifically target Android. -android # The dex compiler ignores preverification information. -dontpreverify # Reduce the size of the output some more. -repackageclasses '' -allowaccessmodification # Switch off some optimizations that trip older versions of the Dalvik VM. -optimizations !code/simplification/arithmetic # Keep a fixed source file attribute and all line number tables to get line # numbers in the stack traces. -renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable ############################################################################### # Settings to handle reflection in the code. ############################################################################### # RemoteViews might need annotations. -keepattributes *Annotation* # Preserve all fundamental application classes. -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider # Preserve all View implementations, their special context constructors, and # their setters. -keep public class * extends android.view.View { public (android.content.Context); public (android.content.Context, android.util.AttributeSet); public (android.content.Context, android.util.AttributeSet, int); public void set*(...); } # Preserve all classes that have special context constructors, and the # constructors themselves. -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet); } # Preserve all classes that have special context constructors, and the # constructors themselves. -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet, int); } # Preserve all possible onClick handlers. -keepclassmembers class * extends android.content.Context { public void *(android.view.View); public void *(android.view.MenuItem); } # Preserve the special fields of all Parcelable implementations. -keepclassmembers class * implements android.os.Parcelable { static android.os.Parcelable$Creator CREATOR; } # Preserve static fields of inner classes of R classes that might be accessed # through introspection. -keepclassmembers class **.R$* { public static ; } # Preserve annotated Javascript interface methods. -keepclassmembers class * { @android.webkit.JavascriptInterface ; } # Preserve annotated and generated classes for Dagger. -keepclassmembers,allowobfuscation class * { @dagger.** *; } -keep class **$$ModuleAdapter -keep class **$$InjectAdapter -keep class **$$StaticInjection -if class **$$ModuleAdapter -keep class <1> -if class **$$InjectAdapter -keep class <1> -if class **$$StaticInjection -keep class <1> -keepnames class dagger.Lazy # Preserve annotated and generated classes for Butterknife. -keep class **$$ViewBinder { public static void bind(...); public static void unbind(...); } -if class **$$ViewBinder -keep class <1> -keep class **_ViewBinding { (<1>, android.view.View); } -if class **_ViewBinding -keep class <1> # Preserve fields that are serialized with GSON. #-keepclassmembers class com.example.SerializedClass1, # com.example.SerializedClass2 { # ; #} -keepclassmembers,allowobfuscation class * { @com.google.gson.annotations.SerializedName ; } -keep,allowobfuscation @interface com.google.gson.annotations.** # Preserve the required interface from the License Verification Library # (but don't nag the developer if the library is not used at all). -keep public interface com.android.vending.licensing.ILicensingService -dontnote com.android.vending.licensing.ILicensingService # The Android Compatibility library references some classes that may not be # present in all versions of the API, but we know that's ok. -dontwarn android.support.** # Preserve all native method names and the names of their classes. -keepclasseswithmembernames,includedescriptorclasses class * { native ; } # Preserve the special static methods that are required in all enumeration # classes. -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Explicitly preserve all serialization members. The Serializable interface # is only a marker interface, so it wouldn't save them. # You can comment this out if your application doesn't use serialization. # If your code contains serializable classes that have to be backward # compatible, please refer to the manual. -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # Your application may contain more items that need to be preserved; # typically classes that are dynamically created using Class.forName: # -keep public class com.example.MyClass # -keep public interface com.example.MyInterface # -keep public class * implements com.example.MyInterface ############################################################################### # Further optimizations. ############################################################################### # If you wish, you can let the optimization step remove Android logging calls. #-assumenosideeffects class android.util.Log { # public static boolean isLoggable(java.lang.String, int); # public static int v(...); # public static int i(...); # public static int w(...); # public static int d(...); # public static int e(...); #} ================================================ FILE: examples/standalone/applets.pro ================================================ # # This ProGuard configuration file illustrates how to process applets. # Usage: # java -jar proguard.jar @applets.pro # -verbose # Specify the input jars, output jars, and library jars. -injars in.jar -outjars out.jar # Before Java 9, the runtime classes were packaged in a single jar file. #-libraryjars /lib/rt.jar # As of Java 9, the runtime classes are packaged in modular jmod files. -libraryjars /jmods/java.base.jmod (!**.jar;!module-info.class) -libraryjars /jmods/java.desktop.jmod(!**.jar;!module-info.class) # Save the obfuscation mapping to a file, so you can de-obfuscate any stack # traces later on. Keep a fixed source file attribute and all line number # tables to get line numbers in the stack traces. # You can comment this out if you're not interested in stack traces. -printmapping out.map -renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable # Preserve all annotations. -keepattributes *Annotation* # You can print out the seeds that are matching the keep options below. #-printseeds out.seeds # Preserve all public applets. -keep public class * extends java.applet.Applet # Preserve all native method names and the names of their classes. -keepclasseswithmembernames,includedescriptorclasses class * { native ; } # Preserve the special static methods that are required in all enumeration # classes. -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Explicitly preserve all serialization members. The Serializable interface # is only a marker interface, so it wouldn't save them. # You can comment this out if your library doesn't use serialization. # If your code contains serializable classes that have to be backward # compatible, please refer to the manual. -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # Your application may contain more items that need to be preserved; # typically classes that are dynamically created using Class.forName: # -keep public class com.example.MyClass # -keep public interface com.example.MyInterface # -keep public class * implements com.example.MyInterface ================================================ FILE: examples/standalone/applications.pro ================================================ # # This ProGuard configuration file illustrates how to process applications. # Usage: # java -jar proguard.jar @applications.pro # -verbose # Specify the input jars, output jars, and library jars. -injars in.jar -outjars out.jar # Before Java 9, the runtime classes were packaged in a single jar file. #-libraryjars /lib/rt.jar # As of Java 9, the runtime classes are packaged in modular jmod files. -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) #-libraryjars /jmods/..... #-libraryjars junit.jar #-libraryjars servlet.jar #-libraryjars jai_core.jar #... # Save the obfuscation mapping to a file, so you can de-obfuscate any stack # traces later on. Keep a fixed source file attribute and all line number # tables to get line numbers in the stack traces. # You can comment this out if you're not interested in stack traces. -printmapping out.map -renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable # Preserve all annotations. -keepattributes *Annotation* # You can print out the seeds that are matching the keep options below. #-printseeds out.seeds # Preserve all public applications. -keepclasseswithmembers public class * { public static void main(java.lang.String[]); } # Preserve all native method names and the names of their classes. -keepclasseswithmembernames,includedescriptorclasses class * { native ; } # Preserve the special static methods that are required in all enumeration # classes. -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Explicitly preserve all serialization members. The Serializable interface # is only a marker interface, so it wouldn't save them. # You can comment this out if your application doesn't use serialization. # If your code contains serializable classes that have to be backward # compatible, please refer to the manual. -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # Your application may contain more items that need to be preserved; # typically classes that are dynamically created using Class.forName: # -keep public class com.example.MyClass # -keep public interface com.example.MyInterface # -keep public class * implements com.example.MyInterface ================================================ FILE: examples/standalone/library.pro ================================================ # # This ProGuard configuration file illustrates how to process a program # library, such that it remains usable as a library. # Usage: # java -jar proguard.jar @library.pro # -verbose # Specify the input jars, output jars, and library jars. # In this case, the input jar is the program library that we want to process. -injars in.jar -outjars out.jar # Before Java 9, the runtime classes were packaged in a single jar file. #-libraryjars /lib/rt.jar # As of Java 9, the runtime classes are packaged in modular jmod files. -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) #-libraryjars /jmods/..... # Save the obfuscation mapping to a file, so we can de-obfuscate any stack # traces later on. Keep a fixed source file attribute and all line number # tables to get line numbers in the stack traces. # You can comment this out if you're not interested in stack traces. -printmapping out.map -keepparameternames -renamesourcefileattribute SourceFile -keepattributes Signature,Exceptions, InnerClasses,PermittedSubclasses,EnclosingMethod, Deprecated,SourceFile,LineNumberTable # Preserve all annotations. -keepattributes *Annotation* # Preserve all public classes, and their public and protected fields and # methods. -keep public class * { public protected *; } # Preserve all .class method names. -keepclassmembernames class * { java.lang.Class class$(java.lang.String); java.lang.Class class$(java.lang.String, boolean); } # Preserve all native method names and the names of their classes. -keepclasseswithmembernames,includedescriptorclasses class * { native ; } # Preserve the special static methods that are required in all enumeration # classes. -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Explicitly preserve all serialization members. The Serializable interface # is only a marker interface, so it wouldn't save them. # You can comment this out if your library doesn't use serialization. # If your code contains serializable classes that have to be backward # compatible, please refer to the manual. -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # Your library may contain more items that need to be preserved; # typically classes that are dynamically created using Class.forName: # -keep public class com.example.MyClass # -keep public interface com.example.MyInterface # -keep public class * implements com.example.MyInterface ================================================ FILE: examples/standalone/midlets.pro ================================================ # # This ProGuard configuration file illustrates how to process J2ME midlets. # Usage: # java -jar proguard.jar @midlets.pro # -verbose # Specify the input jars, output jars, and library jars. -injars in.jar -outjars out.jar -libraryjars /usr/local/java/wtk2.5.2/lib/midpapi20.jar -libraryjars /usr/local/java/wtk2.5.2/lib/cldcapi11.jar # Preverify the code suitably for Java Micro Edition. -microedition # Allow methods with the same signature, except for the return type, # to get the same obfuscation name. -overloadaggressively # Put all obfuscated classes into the nameless root package. -repackageclasses '' # Allow classes and class members to be made public. -allowaccessmodification # On Windows, you can't use mixed case class names, # should you still want to use the preverify tool. # # -dontusemixedcaseclassnames # Save the obfuscation mapping to a file, so you can de-obfuscate any stack # traces later on. -printmapping out.map # You can keep a fixed source file attribute and all line number tables to # get stack traces with line numbers. #-renamesourcefileattribute SourceFile #-keepattributes SourceFile,LineNumberTable # You can print out the seeds that are matching the keep options below. #-printseeds out.seeds # Preserve all public midlets. -keep public class * extends javax.microedition.midlet.MIDlet # Preserve all native method names and the names of their classes. -keepclasseswithmembernames,includedescriptorclasses class * { native ; } # Your midlet may contain more items that need to be preserved; # typically classes that are dynamically created using Class.forName: # -keep public class com.example.MyClass # -keep public interface com.example.MyInterface # -keep public class * implements com.example.MyInterface ================================================ FILE: examples/standalone/proguard.pro ================================================ # # This ProGuard configuration file illustrates how to process ProGuard itself. # Configuration files for typical applications will be very similar. # Usage: # java -jar proguard.jar @proguard.pro # -verbose # Specify the input jars, output jars, and library jars. -injars ../../lib/proguard.jar -outjars proguard_out.jar # Before Java 9, the runtime classes were packaged in a single jar file. #-libraryjars /lib/rt.jar # As of Java 9, the runtime classes are packaged in modular jmod files. -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -libraryjars /jmods/java.sql.jmod (!**.jar;!module-info.class) #-libraryjars /jmods/..... # Write out an obfuscation mapping file, for de-obfuscating any stack traces # later on, or for incremental obfuscation of extensions. -printmapping proguard.map # Don't print notes about reflection in GSON code, the Kotlin runtime, and # our own optionally injected code. -dontnote kotlin.** -dontnote kotlinx.** -dontnote com.google.gson.** -dontnote proguard.configuration.ConfigurationLogger # Preserve injected GSON utility classes and their members. -keep,allowobfuscation class proguard.optimize.gson._* -keepclassmembers class proguard.optimize.gson._* { *; } # Obfuscate class strings of injected GSON utility classes. -adaptclassstrings proguard.optimize.gson.** # Allow methods with the same signature, except for the return type, # to get the same obfuscation name. -overloadaggressively # Put all obfuscated classes into the nameless root package. -repackageclasses '' # Allow classes and class members to be made public. -allowaccessmodification # The entry point: ProGuard and its main method. -keep public class proguard.ProGuard { public static void main(java.lang.String[]); } # If you want to preserve the Ant task as well, you'll have to specify the # main ant.jar. #-libraryjars /usr/local/java/ant/lib/ant.jar #-adaptresourcefilecontents proguard/ant/task.properties # #-keep,allowobfuscation class proguard.ant.* #-keepclassmembers public class proguard.ant.* { # (org.apache.tools.ant.Project); # public void set*(***); # public void add*(***); #} # If you want to preserve the Gradle task, you'll have to specify the Gradle # jars. #-libraryjars /usr/local/java/gradle-4.2.1/lib/plugins/gradle-plugins-4.2.1.jar #-libraryjars /usr/local/java/gradle-4.2.1/lib/gradle-base-services-4.2.1.jar #-libraryjars /usr/local/java/gradle-4.2.1/lib/gradle-core-4.2.1.jar #-libraryjars /usr/local/java/gradle-4.2.1/lib/groovy-all-2.4.12.jar #-keep public class proguard.gradle.* { # public *; #} # If you want to preserve the WTK obfuscation plug-in, you'll have to specify # the kenv.zip file. #-libraryjars /usr/local/java/wtk2.5.2/wtklib/kenv.zip #-keep public class proguard.wtk.ProGuardObfuscator ================================================ FILE: examples/standalone/proguardgui.pro ================================================ # # This ProGuard configuration file illustrates how to process the ProGuard GUI. # Configuration files for typical applications will be very similar. # Usage: # java -jar proguard.jar @proguardgui.pro # -verbose # Specify the input jars, output jars, and library jars. # The input jars will be merged in a single output jar. -injars ../../lib/proguardgui.jar -outjars proguardgui_out.jar # Before Java 9, the runtime classes were packaged in a single jar file. #-libraryjars /lib/rt.jar # As of Java 9, the runtime classes are packaged in modular jmod files. -libraryjars /jmods/java.base.jmod (!**.jar;!module-info.class) -libraryjars /jmods/java.sql.jmod (!**.jar;!module-info.class) -libraryjars /jmods/java.desktop.jmod(!**.jar;!module-info.class) #-libraryjars /jmods/..... # Write out an obfuscation mapping file, for de-obfuscating any stack traces # later on, or for incremental obfuscation of extensions. -printmapping proguardgui.map # If we wanted to reuse the previously obfuscated proguard_out.jar, we could # perform incremental obfuscation based on its mapping file, and only keep the # additional GUI files instead of all files. #-applymapping proguard.map #-injars ../../lib/proguardgui.jar #-outjars proguardgui_out.jar #-libraryjars ../../lib/proguard.jar(!proguard/ant/**,!proguard/wtk/**) #-libraryjars ../../lib/retrace.jar #-libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) #-libraryjars /jmods/java.desktop.jmod(!**.jar;!module-info.class) # Don't print notes about reflection in GSON code, the Kotlin runtime, and # our own optionally injected code. -dontnote kotlin.** -dontnote kotlinx.** -dontnote com.google.gson.** -dontnote proguard.configuration.ConfigurationLogger # Preserve injected GSON utility classes and their members. -keep,allowobfuscation class proguard.optimize.gson._* -keepclassmembers class proguard.optimize.gson._* { *; } # Obfuscate class strings of injected GSON utility classes. -adaptclassstrings proguard.optimize.gson.** # Allow methods with the same signature, except for the return type, # to get the same obfuscation name. -overloadaggressively # Put all obfuscated classes into the nameless root package. -repackageclasses '' # Adapt the names of resource files, based on the corresponding obfuscated # class names. Notably, in this case, the GUI resource properties file will # have to be renamed. -adaptresourcefilenames **.properties,**.gif,**.jpg # The entry point: ProGuardGUI and its main method. -keep public class proguard.gui.ProGuardGUI { public static void main(java.lang.String[]); } ================================================ FILE: examples/standalone/retrace.pro ================================================ # # This ProGuard configuration file illustrates how to process the ReTrace tool. # Configuration files for typical applications will be very similar. # Usage: # java -jar proguard.jar @retrace.pro # -verbose # Specify the input jars, output jars, and library jars. # The input jars will be merged in a single output jar. -injars ../../lib/retrace.jar -outjars retrace_out.jar # Before Java 9, the runtime classes were packaged in a single jar file. #-libraryjars /lib/rt.jar # As of Java 9, the runtime classes are packaged in modular jmod files. -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -libraryjars /jmods/java.sql.jmod (!**.jar;!module-info.class) #-libraryjars /jmods/..... # Write out an obfuscation mapping file, for de-obfuscating any stack traces # later on, or for incremental obfuscation of extensions. -printmapping retrace.map # If we wanted to reuse the previously obfuscated proguard_out.jar, we could # perform incremental obfuscation based on its mapping file, and only keep the # additional ReTrace files instead of all files. #-applymapping proguard.map #-outjars retrace_out.jar(proguard/retrace/**) # Don't print notes about reflection in GSON code, the Kotlin runtime, and # our own optionally injected code. -dontnote kotlin.** -dontnote kotlinx.** -dontnote com.google.gson.** -dontnote proguard.configuration.ConfigurationLogger # Allow methods with the same signature, except for the return type, # to get the same obfuscation name. -overloadaggressively # Put all obfuscated classes into the nameless root package. -repackageclasses '' # Allow classes and class members to be made public. -allowaccessmodification # The entry point: ReTrace and its main method. -keep public class proguard.retrace.ReTrace { public static void main(java.lang.String[]); } ================================================ FILE: examples/standalone/scala.pro ================================================ # # This ProGuard configuration file illustrates how to process Scala # applications, including the Scala runtime. # Usage: # java -jar proguard.jar @scala.pro # -verbose # Specify the input jars, output jars, and library jars. -injars in.jar -injars /usr/local/java/scala-2.9.1/lib/scala-library.jar #-injars /usr/local/java/scala-2.9.1/lib/scala-compiler.jar(!META-INF/MANIFEST.MF) #-injars /usr/local/java/scala-2.9.1/lib/jline.jar(!META-INF/MANIFEST.MF) -outjars out.jar # Before Java 9, the runtime classes were packaged in a single jar file. #-libraryjars /lib/rt.jar # As of Java 9, the runtime classes are packaged in modular jmod files. -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) #-libraryjars /jmods/..... #-libraryjars /usr/local/java/ant/lib/ant.jar #... # Ignore some compiler artefacts. -dontwarn scala.** # Save the obfuscation mapping to a file, so you can de-obfuscate any stack # traces later on. Keep a fixed source file attribute and all line number # tables to get line numbers in the stack traces. # You can comment this out if you're not interested in stack traces. -printmapping out.map -renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable # Preserve all annotations. -keepattributes *Annotation* # You can print out the seeds that are matching the keep options below. #-printseeds out.seeds # Preserve all public applications. -keepclasseswithmembers public class * { public static void main(java.lang.String[]); } # Preserve some classes and class members that are accessed by means of # introspection. -keep class * implements org.xml.sax.EntityResolver -keepclassmembers class * { ** MODULE$; } -keepclassmembernames class scala.concurrent.forkjoin.ForkJoinPool { long eventCount; int workerCounts; int runControl; scala.concurrent.forkjoin.ForkJoinPool$WaitQueueNode syncStack; scala.concurrent.forkjoin.ForkJoinPool$WaitQueueNode spareStack; } -keepclassmembernames class scala.concurrent.forkjoin.ForkJoinWorkerThread { int base; int sp; int runState; } -keepclassmembernames class scala.concurrent.forkjoin.ForkJoinTask { int status; } -keepclassmembernames class scala.concurrent.forkjoin.LinkedTransferQueue { scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference head; scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference tail; scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference cleanMe; } # Preserve some classes and class members that are accessed by means of # introspection in the Scala compiler library, if it is processed as well. #-keep class * implements jline.Completor #-keep class * implements jline.Terminal #-keep class scala.tools.nsc.Global #-keepclasseswithmembers class * { # (scala.tools.nsc.Global); #} #-keepclassmembers class * { # *** scala_repl_value(); # *** scala_repl_result(); #} # Preserve all native method names and the names of their classes. -keepclasseswithmembernames,includedescriptorclasses class * { native ; } # Preserve the special static methods that are required in all enumeration # classes. -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Explicitly preserve all serialization members. The Serializable interface # is only a marker interface, so it wouldn't save them. # You can comment this out if your application doesn't use serialization. # If your code contains serializable classes that have to be backward # compatible, please refer to the manual. -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # Your application may contain more items that need to be preserved; # typically classes that are dynamically created using Class.forName: # -keep public class com.example.MyClass # -keep public interface com.example.MyInterface # -keep public class * implements com.example.MyInterface ================================================ FILE: examples/standalone/servlets.pro ================================================ # # This ProGuard configuration file illustrates how to process servlets. # Usage: # java -jar proguard.jar @servlets.pro # -verbose # Specify the input jars, output jars, and library jars. -injars in.jar -outjars out.jar # Before Java 9, the runtime classes were packaged in a single jar file. #-libraryjars /lib/rt.jar # As of Java 9, the runtime classes are packaged in modular jmod files. -libraryjars /jmods/java.base.jmod(!**.jar;!module-info.class) -libraryjars /usr/local/java/servlet/servlet.jar # Save the obfuscation mapping to a file, so you can de-obfuscate any stack # traces later on. Keep a fixed source file attribute and all line number # tables to get line numbers in the stack traces. # You can comment this out if you're not interested in stack traces. -printmapping out.map -renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable # Preserve all annotations. -keepattributes *Annotation* # You can print out the seeds that are matching the keep options below. #-printseeds out.seeds # Preserve all public servlets. -keep public class * implements javax.servlet.Servlet # Preserve all native method names and the names of their classes. -keepclasseswithmembernames,includedescriptorclasses class * { native ; } # Preserve the special static methods that are required in all enumeration # classes. -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Explicitly preserve all serialization members. The Serializable interface # is only a marker interface, so it wouldn't save them. # You can comment this out if your library doesn't use serialization. # If your code contains serializable classes that have to be backward # compatible, please refer to the manual. -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # Your application may contain more items that need to be preserved; # typically classes that are dynamically created using Class.forName: # -keep public class com.example.MyClass # -keep public interface com.example.MyInterface # -keep public class * implements com.example.MyInterface ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle-plugin/.gitignore ================================================ build/* .gradle/* .idea/ ================================================ FILE: gradle-plugin/build.gradle ================================================ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { id 'com.github.johnrengelman.shadow' id 'java-gradle-plugin' id 'org.jetbrains.kotlin.jvm' version "$kotlinVersion" id 'maven-publish' id 'org.jlleitschuh.gradle.ktlint' version '9.2.1' } repositories { google() mavenCentral() } def agpVersion = '4.1.0' gradlePlugin { plugins { proguardPlugin { id = 're.obfuscator.dprotect' implementationClass = 'proguard.gradle.plugin.ProGuardPlugin' } } } compileKotlin { kotlinOptions.jvmTarget = "1.8" } compileTestKotlin { kotlinOptions.jvmTarget = "1.8" } configurations { fatJar.extendsFrom(compile) } dependencies { api project(':base') // TODO update to 4.2 or 7.0 API compileOnly ("com.android.tools.build:gradle:$agpVersion") { exclude module: 'proguard-gradle' exclude module: 'proguard-base' } implementation 'com.github.zafarkhaja:java-semver:0.9.0' testCompileOnly ("com.android.tools.build:gradle:$agpVersion") { exclude module: 'proguard-gradle' exclude module: 'proguard-base' } testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.0' // for kotest framework testImplementation 'io.kotest:kotest-assertions-core-jvm:4.6.0' // for kotest core jvm assertions testImplementation 'io.kotest:kotest-property-jvm:4.6.0' // for kotest property test testImplementation "io.mockk:mockk:1.12.0" testImplementation "commons-io:commons-io:2.8.0" fatJar project(":base") // This library is used to parse the android gradle plugin version at runtime. fatJar 'com.github.zafarkhaja:java-semver:0.9.0' } test { useJUnitPlatform() } def localRepo = file("$buildDir/local-repo") task fatJar(type: ShadowJar) { destinationDirectory.set(file("$rootDir/lib")) archiveFileName.set("dprotect-gradle-${version}.jar") from sourceSets.main.output configurations = [project.configurations.fatJar] manifest { attributes( 'Main-Class': 'dprotect.DProtect', 'Implementation-Version': archiveVersion.get()) } } tasks.withType(Test).configureEach { dependsOn fatJar systemProperty "local.repo", localRepo.toURI() systemProperty "proguard.version", version systemProperty "agp.version", agpVersion } sourceSets.test { resources { srcDirs += "$buildDir/fixtures" } } // Copies selected directories from examples into the test resources to compile during an integration test. def copyExamples = tasks.register('copyExamples', Copy) { from("$rootProject.rootDir/examples") { include 'spring-boot/**' include 'gradle-kotlin-dsl/**' include 'application/**' } into "$buildDir/fixtures" } tasks.named('processTestResources', ProcessResources) { dependsOn copyExamples } tasks.withType(Test).configureEach { dependsOn copyExamples } afterEvaluate { /* This project gets two publications: - "pluginMaven" is the default from `java-gradle-plugin` and must not be automatically published. - "" is created by ProGuard custom logic and should have its artifact ID adjusted. - we also disable publishing of plugin markers. */ tasks.withType(AbstractPublishToMaven).matching { task -> task.publication.name == 'pluginMaven' || task.name =~ /^publish(.*)PluginMarkerMavenPublicationTo(GithubRepository|MavenLocal|SonatypeRepository)$/ } each {task -> task.enabled = false } publishing { publications.getByName(project.name) { pom { description = 'Gradle plugin for dProtect, the enhanced Android code obfuscator based on Proguard™' } } } } ================================================ FILE: gradle-plugin/src/main/java/proguard/gradle/ProGuardTask.java ================================================ /* * 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.gradle; import groovy.lang.Closure; import org.gradle.api.DefaultTask; import org.gradle.api.file.*; import org.gradle.api.logging.*; import org.gradle.api.tasks.*; import org.gradle.api.tasks.Optional; import org.gradle.util.GradleVersion; import dprotect.*; import proguard.ParseException; import proguard.ClassSpecification; import proguard.MemberSpecification; import proguard.ClassPathEntry; import proguard.ClassPath; import proguard.MemberValueSpecification; import proguard.KeepClassSpecification; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import proguard.util.ListUtil; import java.io.*; import java.net.*; import java.util.*; /** * This Task allows to configure and run ProGuard from Gradle. * * @author Eric Lafortune */ @CacheableTask public abstract class ProGuardTask extends DefaultTask { // Accumulated input and output, for the sake of Gradle's lazy file // resolution and lazy task execution. private final List inJarFiles = new ArrayList(); private final List inJarFilters = new ArrayList(); private final List outJarFiles = new ArrayList(); private final List outJarFilters = new ArrayList(); private final List inJarCounts = new ArrayList(); private final List libraryJarFiles = new ArrayList(); private final List libraryJarFilters = new ArrayList(); private final List configurationFiles = new ArrayList(); // Accumulated configuration. protected final Configuration configuration = new Configuration(); // Fields acting as parameters for the class member specification methods. private boolean allowValues; private ClassSpecification classSpecification; public static final String DEFAULT_CONFIG_RESOURCE_PREFIX = "/lib"; private final String resolvedConfigurationFileNamePrefix = getProject().file(DEFAULT_CONFIG_RESOURCE_PREFIX).toString(); // INTERNAL USE ONLY - write extra data entries to this jar private File extraJar; public ProGuardTask() { if (GradleVersion.current().compareTo(GradleVersion.version("7.4")) >= 0) { // This method was added in Gradle 7.4 notCompatibleWithConfigurationCache("https://github.com/Guardsquare/proguard/issues/254"); } } // Gradle task inputs and outputs, because annotations on the List fields // (private or not) don't seem to work. Private methods don't work either, // but package visible or protected methods are ok. @Classpath public FileCollection getInJarFileCollection() { return getProject().files(inJarFiles); } @OutputFiles public FileCollection getOutJarFileCollection() { return getProject().files(outJarFiles); } @Classpath public FileCollection getLibraryJarFileCollection() { return getProject().files(libraryJarFiles); } @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getConfigurationFileCollection() { return getProject().files(configurationFiles); } // Convenience methods to retrieve settings from outside the task. /** * Returns the collected list of input files (directory, jar, aar, etc, * represented as Object, String, File, etc). */ @Internal public List getInJarFiles() { return inJarFiles; } /** * Returns the collected list of filters (represented as argument Maps) * corresponding to the list of input files. */ @Input public List getInJarFilters() { return inJarFilters; } /** * Returns the collected list of output files (directory, jar, aar, etc, * represented as Object, String, File, etc). */ @Internal public List getOutJarFiles() { return outJarFiles; } /** * Returns the collected list of filters (represented as argument Maps) * corresponding to the list of output files. */ @Input public List getOutJarFilters() { return outJarFilters; } /** * Returns the list with the numbers of input files that correspond to the * list of output files. * * For instance, [2, 3] means that * the contents of the first 2 input files go to the first output file and * the contents of the next 3 input files go to the second output file. */ @Internal public List getInJarCounts() { return inJarCounts; } /** * Returns the collected list of library files (directory, jar, aar, etc, * represented as Object, String, File, etc). */ @Internal public List getLibraryJarFiles() { return libraryJarFiles; } /** * Returns the collected list of filters (represented as argument Maps) * corresponding to the list of library files. */ @Input public List getLibraryJarFilters() { return libraryJarFilters; } /** * Returns the collected list of configuration files to be included * (represented as Object, String, File, etc). */ @Internal public List getConfigurationFiles() { return configurationFiles; } // Gradle task settings corresponding to all ProGuard options. public void configuration(Object configurationFiles) throws ParseException, IOException { // Just collect the arguments, so they can be resolved lazily. // Flatten collections, so they are easier to analyze later on. if (configurationFiles instanceof Collection) { this.configurationFiles.addAll((Collection)configurationFiles); } else { this.configurationFiles.add(configurationFiles); } } public void injars(Object inJarFiles) throws ParseException { injars(null, inJarFiles); } public void injars(Map filterArgs, Object inJarFiles) throws ParseException { // Just collect the arguments, so they can be resolved lazily. this.inJarFiles.add(inJarFiles); this.inJarFilters.add(filterArgs); } public void outjars(Object outJarFiles) throws ParseException { outjars(null, outJarFiles); } public void outjars(Map filterArgs, Object outJarFiles) throws ParseException { // Just collect the arguments, so they can be resolved lazily. this.outJarFiles.add(outJarFiles); this.outJarFilters.add(filterArgs); this.inJarCounts.add(Integer.valueOf(inJarFiles.size())); } public void libraryjars(Object libraryJarFiles) throws ParseException { libraryjars(null, libraryJarFiles); } public void libraryjars(Map filterArgs, Object libraryJarFiles) throws ParseException { // Just collect the arguments, so they can be resolved lazily. this.libraryJarFiles.add(libraryJarFiles); this.libraryJarFilters.add(filterArgs); } public void extraJar(File extraJar) { configuration.extraJar = extraJar; } // Hack: support the keyword without parentheses in Groovy. @Internal public Object getskipnonpubliclibraryclasses() { skipnonpubliclibraryclasses(); return null; } public void skipnonpubliclibraryclasses() { configuration.skipNonPublicLibraryClasses = true; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getdontskipnonpubliclibraryclassmembers() { dontskipnonpubliclibraryclassmembers(); return null; } public void dontskipnonpubliclibraryclassmembers() { configuration.skipNonPublicLibraryClassMembers = false; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getkeepdirectories() { keepdirectories(); return null; } public void keepdirectories() { keepdirectories(null); } public void keepdirectories(String filter) { configuration.keepDirectories = extendFilter(configuration.keepDirectories, filter); } public void target(String targetClassVersion) { configuration.targetClassVersion = ClassUtil.internalClassVersion(targetClassVersion); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getforceprocessing() { forceprocessing(); return null; } public void forceprocessing() { configuration.lastModified = Long.MAX_VALUE; } public void keep(String classSpecificationString) throws ParseException { keep(null, classSpecificationString); } public void keep(Map keepArgs, String classSpecificationString) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(false, false, false, true, true, true, false, false, keepArgs, classSpecificationString)); } public void keep(Map keepClassSpecificationArgs) throws ParseException { keep(keepClassSpecificationArgs, (Closure)null); } public void keep(Map keepClassSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(false, false, false, true, true, true, false, false, keepClassSpecificationArgs, classMembersClosure)); } public void keepclassmembers(String classSpecificationString) throws ParseException { keepclassmembers(null, classSpecificationString); } public void keepclassmembers(Map keepArgs, String classSpecificationString) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(false, false, false, true, false, true, false, false, keepArgs, classSpecificationString)); } public void keepclassmembers(Map keepClassSpecificationArgs) throws ParseException { keepclassmembers(keepClassSpecificationArgs, (Closure)null); } public void keepclassmembers(Map keepClassSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(false, false, false, true, false, true, false, false, keepClassSpecificationArgs, classMembersClosure)); } public void keepclasseswithmembers(String classSpecificationString) throws ParseException { keepclasseswithmembers(null, classSpecificationString); } public void keepclasseswithmembers(Map keepArgs, String classSpecificationString) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(false, false, false, true, false, true, false, true, keepArgs, classSpecificationString)); } public void keepclasseswithmembers(Map keepClassSpecificationArgs) throws ParseException { keepclasseswithmembers(keepClassSpecificationArgs, (Closure)null); } public void keepclasseswithmembers(Map keepClassSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(false, false, false, true, false, true, false, true, keepClassSpecificationArgs, classMembersClosure)); } public void keepnames(String classSpecificationString) throws ParseException { keepnames(null, classSpecificationString); } public void keepnames(Map keepArgs, String classSpecificationString) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(true, false, false, true, true, true, false, false, keepArgs, classSpecificationString)); } public void keepnames(Map keepClassSpecificationArgs) throws ParseException { keepnames(keepClassSpecificationArgs, (Closure)null); } public void keepnames(Map keepClassSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(true, false, false, true, true, true, false, false, keepClassSpecificationArgs, classMembersClosure)); } public void keepclassmembernames(String classSpecificationString) throws ParseException { keepclassmembernames(null, classSpecificationString); } public void keepclassmembernames(Map keepArgs, String classSpecificationString) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(true, false, false, true, false, true, false, false, keepArgs, classSpecificationString)); } public void keepclassmembernames(Map keepClassSpecificationArgs) throws ParseException { keepclassmembernames(keepClassSpecificationArgs, (Closure)null); } public void keepclassmembernames(Map keepClassSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(true, false, false, true, false, true, false, false, keepClassSpecificationArgs, classMembersClosure)); } public void keepclasseswithmembernames(String classSpecificationString) throws ParseException { keepclasseswithmembernames(null, classSpecificationString); } public void keepclasseswithmembernames(Map keepArgs, String classSpecificationString) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(true, false, false, true, false, true, false, true, keepArgs, classSpecificationString)); } public void keepclasseswithmembernames(Map keepClassSpecificationArgs) throws ParseException { keepclasseswithmembernames(keepClassSpecificationArgs, (Closure)null); } public void keepclasseswithmembernames(Map keepClassSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(true, false, false, true, false, true, false, true, keepClassSpecificationArgs, classMembersClosure)); } public void keepcode(String classSpecificationString) throws ParseException { keepcode(null, classSpecificationString); } public void keepcode(Map keepArgs, String classSpecificationString) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(true, false, false, true, false, false, true, true, keepArgs, classSpecificationString)); } public void keepcode(Map keepClassSpecificationArgs) throws ParseException { keepcode(keepClassSpecificationArgs, (Closure)null); } public void keepcode(Map keepClassSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.keep = extendClassSpecifications(configuration.keep, createKeepClassSpecification(true, false, false, true, false, false, true, true, keepClassSpecificationArgs, classMembersClosure)); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getprintseeds() { printseeds(); return null; } public void printseeds() { configuration.printSeeds = Configuration.STD_OUT; } public void printseeds(Object printSeeds) throws ParseException { configuration.printSeeds = getProject().file(printSeeds); } @Optional @OutputFile public File getPrintSeedsFile() { return optionalFile(configuration.printSeeds); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getdontshrink() { dontshrink(); return null; } public void dontshrink() { configuration.shrink = false; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getprintusage() { printusage(); return null; } public void printusage() { configuration.printUsage = Configuration.STD_OUT; } public void printusage(Object printUsage) throws ParseException { configuration.printUsage = getProject().file(printUsage); } @Optional @OutputFile public File getPrintUsageFile() { return optionalFile(configuration.printUsage); } public void whyareyoukeeping(String classSpecificationString) throws ParseException { configuration.whyAreYouKeeping = extendClassSpecifications(configuration.whyAreYouKeeping, createClassSpecification(false, classSpecificationString)); } public void whyareyoukeeping(Map classSpecificationArgs) throws ParseException { whyareyoukeeping(classSpecificationArgs, null); } public void whyareyoukeeping(Map classSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.whyAreYouKeeping = extendClassSpecifications(configuration.whyAreYouKeeping, createClassSpecification(false, classSpecificationArgs, classMembersClosure)); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getdontoptimize() { dontoptimize(); return null; } public void dontoptimize() { configuration.optimize = false; } public void optimizations(String filter) { configuration.optimizations = extendFilter(configuration.optimizations, filter); } public void optimizationpasses(int optimizationPasses) { configuration.optimizationPasses = optimizationPasses; } public void assumenosideeffects(String classSpecificationString) throws ParseException { configuration.assumeNoSideEffects = extendClassSpecifications(configuration.assumeNoSideEffects, createClassSpecification(true, classSpecificationString)); } public void assumenosideeffects(Map classSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.assumeNoSideEffects = extendClassSpecifications(configuration.assumeNoSideEffects, createClassSpecification(true, classSpecificationArgs, classMembersClosure)); } public void assumenoexternalsideeffects(String classSpecificationString) throws ParseException { configuration.assumeNoExternalSideEffects = extendClassSpecifications(configuration.assumeNoExternalSideEffects, createClassSpecification(true, classSpecificationString)); } public void assumenoexternalsideeffects(Map classSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.assumeNoExternalSideEffects = extendClassSpecifications(configuration.assumeNoExternalSideEffects, createClassSpecification(true, classSpecificationArgs, classMembersClosure)); } public void assumenoescapingparameters(String classSpecificationString) throws ParseException { configuration.assumeNoEscapingParameters = extendClassSpecifications(configuration.assumeNoEscapingParameters, createClassSpecification(true, classSpecificationString)); } public void assumenoescapingparameters(Map classSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.assumeNoEscapingParameters = extendClassSpecifications(configuration.assumeNoEscapingParameters, createClassSpecification(true, classSpecificationArgs, classMembersClosure)); } public void assumenoexternalreturnvalues(String classSpecificationString) throws ParseException { configuration.assumeNoExternalReturnValues = extendClassSpecifications(configuration.assumeNoExternalReturnValues, createClassSpecification(true, classSpecificationString)); } public void assumenoexternalreturnvalues(Map classSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.assumeNoExternalReturnValues = extendClassSpecifications(configuration.assumeNoExternalReturnValues, createClassSpecification(true, classSpecificationArgs, classMembersClosure)); } public void assumevalues(String classSpecificationString) throws ParseException { configuration.assumeValues = extendClassSpecifications(configuration.assumeValues, createClassSpecification(true, classSpecificationString)); } public void assumevalues(Map classSpecificationArgs, Closure classMembersClosure) throws ParseException { configuration.assumeValues = extendClassSpecifications(configuration.assumeValues, createClassSpecification(true, classSpecificationArgs, classMembersClosure)); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getallowaccessmodification() { allowaccessmodification(); return null; } public void allowaccessmodification() { configuration.allowAccessModification = true; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getmergeinterfacesaggressively() { mergeinterfacesaggressively(); return null; } public void mergeinterfacesaggressively() { configuration.mergeInterfacesAggressively = true; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getdontobfuscate() { dontobfuscate(); return null; } public void dontobfuscate() { configuration.obfuscate = false; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getprintmapping() { printmapping(); return null; } public void printmapping() { configuration.printMapping = Configuration.STD_OUT; } public void printmapping(Object printMapping) throws ParseException { configuration.printMapping = getProject().file(printMapping); } @Optional @OutputFile public File getPrintMappingFile() { return optionalFile(configuration.printMapping); } public void applymapping(Object applyMapping) throws ParseException { configuration.applyMapping = getProject().file(applyMapping); } public void obfuscationdictionary(Object obfuscationDictionary) throws ParseException, MalformedURLException { configuration.obfuscationDictionary = url(obfuscationDictionary); } public void classobfuscationdictionary(Object classObfuscationDictionary) throws ParseException, MalformedURLException { configuration.classObfuscationDictionary = url(classObfuscationDictionary); } public void packageobfuscationdictionary(Object packageObfuscationDictionary) throws ParseException, MalformedURLException { configuration.packageObfuscationDictionary = url(packageObfuscationDictionary); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getoverloadaggressively() { overloadaggressively(); return null; } public void overloadaggressively() { configuration.overloadAggressively = true; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getuseuniqueclassmembernames() { useuniqueclassmembernames(); return null; } public void useuniqueclassmembernames() { configuration.useUniqueClassMemberNames = true; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getdontusemixedcaseclassnames() { dontusemixedcaseclassnames(); return null; } public void dontusemixedcaseclassnames() { configuration.useMixedCaseClassNames = false; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getkeeppackagenames() { keeppackagenames(); return null; } public void keeppackagenames() { keeppackagenames(null); } public void keeppackagenames(String filter) { configuration.keepPackageNames = extendFilter(configuration.keepPackageNames, filter, true); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getflattenpackagehierarchy() { flattenpackagehierarchy(); return null; } public void flattenpackagehierarchy() { flattenpackagehierarchy(""); } public void flattenpackagehierarchy(String flattenPackageHierarchy) { configuration.flattenPackageHierarchy = ClassUtil.internalClassName(flattenPackageHierarchy); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getrepackageclasses() { repackageclasses(); return null; } public void repackageclasses() { repackageclasses(""); } public void repackageclasses(String repackageClasses) { configuration.repackageClasses = ClassUtil.internalClassName(repackageClasses); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getkeepattributes() { keepattributes(); return null; } public void keepattributes() { keepattributes(null); } public void keepattributes(String filter) { configuration.keepAttributes = extendFilter(configuration.keepAttributes, filter); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getkeepparameternames() { keepparameternames(); return null; } public void keepparameternames() { configuration.keepParameterNames = true; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getrenamesourcefileattribute() { renamesourcefileattribute(); return null; } public void renamesourcefileattribute() { renamesourcefileattribute(""); } public void renamesourcefileattribute(String newSourceFileAttribute) { configuration.newSourceFileAttribute = newSourceFileAttribute; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getadaptclassstrings() { adaptclassstrings(); return null; } public void adaptclassstrings() { adaptclassstrings(null); } public void adaptclassstrings(String filter) { configuration.adaptClassStrings = extendFilter(configuration.adaptClassStrings, filter, true); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getkeepkotlinmetadata() { keepkotlinmetadata(); return null; } public void keepkotlinmetadata() { configuration.keepKotlinMetadata = true; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getadaptresourcefilenames() { adaptresourcefilenames(); return null; } public void adaptresourcefilenames() { adaptresourcefilenames(null); } public void adaptresourcefilenames(String filter) { configuration.adaptResourceFileNames = extendFilter(configuration.adaptResourceFileNames, filter); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getadaptresourcefilecontents() { adaptresourcefilecontents(); return null; } public void adaptresourcefilecontents() { adaptresourcefilecontents(null); } public void adaptresourcefilecontents(String filter) { configuration.adaptResourceFileContents = extendFilter(configuration.adaptResourceFileContents, filter); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getdontpreverify() { dontpreverify(); return null; } public void dontpreverify() { configuration.preverify = false; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getmicroedition() { microedition(); return null; } public void microedition() { configuration.microEdition = true; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getandroid() { android(); return null; } public void android() { configuration.android = true; } public void keystore(Object keyStore) { configuration.keyStores = extendList(configuration.keyStores, getProject().file(keyStore)); } public void keystorepassword(String keyStorePassword) { configuration.keyStorePasswords = extendList(configuration.keyStorePasswords, keyStorePassword); } public void keyalias(String keyAlias) { configuration.keyAliases = extendList(configuration.keyAliases, keyAlias); } public void keypassword(String keyPassword) { configuration.keyPasswords = extendList(configuration.keyPasswords, keyPassword); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getverbose() { verbose(); return null; } public void verbose() { configuration.verbose = true; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getdontnote() { dontnote(); return null; } public void dontnote() { dontnote(null); } public void dontnote(String filter) { configuration.note = extendFilter(configuration.note, filter, true); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getdontwarn() { dontwarn(); return null; } public void dontwarn() { dontwarn(null); } public void dontwarn(String filter) { configuration.warn = extendFilter(configuration.warn, filter, true); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getignorewarnings() { ignorewarnings(); return null; } public void ignorewarnings() { configuration.ignoreWarnings = true; } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getprintconfiguration() { printconfiguration(); return null; } public void printconfiguration() { configuration.printConfiguration = Configuration.STD_OUT; } public void printconfiguration(Object printConfiguration) throws ParseException { configuration.printConfiguration = getProject().file(printConfiguration); } @Optional @OutputFile public File getPrintConfigurationFile() { return optionalFile(configuration.printConfiguration); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getdump() { dump(); return null; } public void dump() { configuration.dump = Configuration.STD_OUT; } public void dump(Object dump) throws ParseException { configuration.dump = getProject().file(dump); } @Optional @OutputFile public File getDumpFile() { return optionalFile(configuration.dump); } @Internal // Hack: support the keyword without parentheses in Groovy. public Object getaddconfigurationdebugging() { addconfigurationdebugging(); return null; } public void addconfigurationdebugging() { configuration.addConfigurationDebugging = true; } // Class member methods. public void field(Map memberSpecificationArgs) throws ParseException { if (classSpecification == null) { throw new IllegalArgumentException("The 'field' method can only be used nested inside a class specification."); } classSpecification.addField(createMemberSpecification(false, false, allowValues, memberSpecificationArgs)); } public void constructor(Map memberSpecificationArgs) throws ParseException { if (classSpecification == null) { throw new IllegalArgumentException("The 'constructor' method can only be used nested inside a class specification."); } classSpecification.addMethod(createMemberSpecification(true, true, allowValues, memberSpecificationArgs)); } public void method(Map memberSpecificationArgs) throws ParseException { if (classSpecification == null) { throw new IllegalArgumentException("The 'method' method can only be used nested inside a class specification."); } classSpecification.addMethod(createMemberSpecification(true, false, allowValues, memberSpecificationArgs)); } // Gradle task execution. @TaskAction public void proguard() throws Exception { // Let the logging manager capture the standard output and errors from // ProGuard. LoggingManager loggingManager = getLogging(); loggingManager.captureStandardOutput(LogLevel.INFO); loggingManager.captureStandardError(LogLevel.WARN); // Run ProGuard with the collected configuration. new DProtect(getConfiguration()).execute(); } /** * Returns the configuration collected so far, resolving files and * reading included configurations. */ private Configuration getConfiguration() throws Exception { // Weave the input jars and the output jars into a single class path, // with lazy resolution of the files. configuration.programJars = new ClassPath(); int outJarIndex = 0; int inJarCount = inJarCounts.size() == 0 ? -1 : ((Integer)inJarCounts.get(0)).intValue(); for (int inJarIndex = 0; inJarIndex < inJarFiles.size(); inJarIndex++) { configuration.programJars = extendClassPath(configuration.programJars, inJarFiles.get(inJarIndex), (Map)inJarFilters.get(inJarIndex), false); while (inJarIndex == inJarCount - 1) { configuration.programJars = extendClassPath(configuration.programJars, outJarFiles.get(outJarIndex), (Map)outJarFilters.get(outJarIndex), true); outJarIndex++; inJarCount = inJarCounts.size() == outJarIndex ? -1 : ((Integer)inJarCounts.get(outJarIndex)).intValue(); } } // Copy the library jars into a single class path, with lazy resolution // of the files. configuration.libraryJars = new ClassPath(); for (int libraryJarIndex = 0; libraryJarIndex < libraryJarFiles.size(); libraryJarIndex++) { configuration.libraryJars = extendClassPath(configuration.libraryJars, libraryJarFiles.get(libraryJarIndex), (Map)libraryJarFilters.get(libraryJarIndex), false); } // Lazily apply the external configuration files. for (int index = 0; index < configurationFiles.size(); index++) { Object fileObject = configurationFiles.get(index); ConfigurableFileCollection fileCollection = getProject().files(fileObject); // Parse the configuration as a collection of files. Iterator files = fileCollection.iterator(); while (files.hasNext()) { File file = files.next(); // Check if this is the name of an internal configuration file. if (isInternalConfigurationFile(file)) { getLogger().info("Loading default configuration file " + file.getAbsolutePath()); String internalConfigFileName = internalConfigurationFileName(file); URL configurationURL = ProGuardTask.class.getResource(internalConfigFileName); if (configurationURL == null) { throw new FileNotFoundException("'" + file.getAbsolutePath() + "'"); } // Parse the configuration as a URL. ConfigurationParser parser = new ConfigurationParser(configurationURL, System.getProperties()); try { parser.parse(configuration); } finally { parser.close(); } } else { getLogger().info("Loading configuration file " + file.getAbsolutePath()); ConfigurationParser parser = new ConfigurationParser(file, System.getProperties()); try { parser.parse(configuration); } finally { parser.close(); } } } } // Make sure the code is processed. Gradle has already checked that it // was necessary. configuration.lastModified = Long.MAX_VALUE; return configuration; } // Small utility methods. /** * Returns whether the given file object is an internal configuration * file that is packaged in a jar, for instance * "/lib/proguard-android-debug.pro". */ private boolean isInternalConfigurationFile(File fileObject) { return fileObject.toString().startsWith(resolvedConfigurationFileNamePrefix); } /** * Returns the name of the internal configuration file that is packaged in * a jar, for instance "/lib/proguard-android-debug.pro". */ private String internalConfigurationFileName(File file) { // Trim any added file prefix (like "C:"). // Make sure we have forward slashes. return file.toString() .substring(resolvedConfigurationFileNamePrefix.length() - DEFAULT_CONFIG_RESOURCE_PREFIX.length()) .replace('\\', '/'); } /** * Extends the given class path with the given filtered input or output * files. */ private ClassPath extendClassPath(ClassPath classPath, Object files, Map filterArgs, boolean output) { ConfigurableFileCollection fileCollection = getProject().files(files); if (classPath == null) { classPath = new ClassPath(); } Iterator fileIterator = fileCollection.iterator(); while (fileIterator.hasNext()) { File file = (File)fileIterator.next(); if (output || file.exists()) { // Create the class path entry. ClassPathEntry classPathEntry = new ClassPathEntry(file, output); // Add the optional feature name and filters to the class path entry. if (filterArgs != null) { classPathEntry.setFeatureName((String)filterArgs.get("feature")); classPathEntry.setFilter(ListUtil.commaSeparatedList((String)filterArgs.get("filter"))); classPathEntry.setApkFilter(ListUtil.commaSeparatedList((String)filterArgs.get("apkfilter"))); classPathEntry.setAabFilter(ListUtil.commaSeparatedList((String)filterArgs.get("aabfilter"))); classPathEntry.setJarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("jarfilter"))); classPathEntry.setAarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("aarfilter"))); classPathEntry.setWarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("warfilter"))); classPathEntry.setEarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("earfilter"))); classPathEntry.setJmodFilter(ListUtil.commaSeparatedList((String)filterArgs.get("jmodfilter"))); classPathEntry.setZipFilter(ListUtil.commaSeparatedList((String)filterArgs.get("zipfilter"))); } classPath.add(classPathEntry); } } return classPath; } /** * Creates specifications to keep classes and class members, based on the * given parameters. */ private KeepClassSpecification createKeepClassSpecification(boolean allowShrinking, boolean allowOptimization, boolean allowObfuscation, boolean allowMultidexing, boolean markClasses, boolean markClassMebers, boolean markCodeAttributes, boolean markConditionally, Map keepArgs, String classSpecificationString) throws ParseException { ClassSpecification condition = createIfClassSpecification(keepArgs); ClassSpecification classSpecification = createClassSpecification(false, classSpecificationString); return createKeepClassSpecification(allowShrinking, allowOptimization, allowObfuscation, allowMultidexing, markClasses, markClassMebers, markCodeAttributes, markConditionally, keepArgs, condition, classSpecification); } /** * Creates specifications to keep classes and class members, based on the * given parameters. */ private KeepClassSpecification createKeepClassSpecification(boolean allowShrinking, boolean allowOptimization, boolean allowObfuscation, boolean allowMultidexing, boolean markClasses, boolean markClassMebers, boolean markCodeAttributes, boolean markConditionally, Map classSpecificationArgs, Closure classMembersClosure) throws ParseException { ClassSpecification condition = createIfClassSpecification(classSpecificationArgs); ClassSpecification classSpecification = createClassSpecification(false, classSpecificationArgs, classMembersClosure); return createKeepClassSpecification(allowShrinking, allowOptimization, allowObfuscation, allowMultidexing, markClasses, markClassMebers, markCodeAttributes, markConditionally, classSpecificationArgs, condition, classSpecification); } /** * Creates a conditional class specification, based on the given * parameters. */ private ClassSpecification createIfClassSpecification(Map classSpecificationArgs) throws ParseException { if (classSpecificationArgs == null) { return null; } String conditionString = (String)classSpecificationArgs.get("if"); if (conditionString == null) { return null; } return createClassSpecification(false, conditionString); } /** * Creates specifications to keep classes and class members, based on the * given parameters. */ private KeepClassSpecification createKeepClassSpecification(boolean allowShrinking, boolean allowOptimization, boolean allowObfuscation, boolean allowMultidexing, boolean markClasses, boolean markClassMebers, boolean markCodeAttributes, boolean markConditionally, Map keepArgs, ClassSpecification condition, ClassSpecification classSpecification) { return new KeepClassSpecification(markClasses, markClassMebers, markConditionally, retrieveBoolean(keepArgs, "includedescriptorclasses", false), retrieveBoolean(keepArgs, "includecode", false) || markCodeAttributes, retrieveBoolean(keepArgs, "allowshrinking", allowShrinking), retrieveBoolean(keepArgs, "allowoptimization", allowOptimization), retrieveBoolean(keepArgs, "allowobfuscation", allowObfuscation), condition, classSpecification); } /** * Creates specifications to keep classes and class members, based on the * given ProGuard-style class specification. */ private ClassSpecification createClassSpecification(boolean allowValues, String classSpecificationString) throws ParseException { try { ConfigurationParser parser = new ConfigurationParser(new String[] { classSpecificationString }, null); try { return parser.parseClassSpecificationArguments(false, true, allowValues); } finally { parser.close(); } } catch (IOException e) { throw new ParseException(e.getMessage()); } } /** * Creates a specification of classes and class members, based on the * given parameters. */ private ClassSpecification createClassSpecification(boolean allowValues, Map classSpecificationArgs, Closure classMembersClosure) throws ParseException { // Extract the arguments. String access = (String)classSpecificationArgs.get("access"); String annotation = (String)classSpecificationArgs.get("annotation"); String type = (String)classSpecificationArgs.get("type"); String name = (String)classSpecificationArgs.get("name"); String extendsAnnotation = (String)classSpecificationArgs.get("extendsannotation"); String extends_ = (String)classSpecificationArgs.get("extends"); if (extends_ == null) { extends_ = (String)classSpecificationArgs.get("implements"); } // Create the class specification. ClassSpecification classSpecification = new ClassSpecification(null, requiredClassAccessFlags(true, access, type), requiredClassAccessFlags(false, access, type), annotation != null ? ClassUtil.internalType(annotation) : null, name != null ? ClassUtil.internalClassName(name) : null, extendsAnnotation != null ? ClassUtil.internalType(extendsAnnotation) : null, extends_ != null ? ClassUtil.internalClassName(extends_) : null); // Initialize the class specification with its closure. if (classMembersClosure != null) { // Temporarily remember the class specification, so we can add // class member specifications. this.allowValues = allowValues; this.classSpecification = classSpecification; classMembersClosure.call(classSpecification); this.allowValues = false; this.classSpecification = null; } return classSpecification; } /** * Parses the class access flags that must be set (or not), based on the * given ProGuard-style flag specification. */ private int requiredClassAccessFlags(boolean set, String access, String type) throws ParseException { int accessFlags = 0; if (access != null) { StringTokenizer tokenizer = new StringTokenizer(access, " ,"); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.startsWith("!") ^ set) { String strippedToken = token.startsWith("!") ? token.substring(1) : token; int accessFlag = strippedToken.equals(JavaAccessConstants.PUBLIC) ? AccessConstants.PUBLIC : strippedToken.equals(JavaAccessConstants.FINAL) ? AccessConstants.FINAL : strippedToken.equals(JavaAccessConstants.ABSTRACT) ? AccessConstants.ABSTRACT : strippedToken.equals(JavaAccessConstants.SYNTHETIC) ? AccessConstants.SYNTHETIC : strippedToken.equals(JavaAccessConstants.ANNOTATION) ? AccessConstants.ANNOTATION : 0; if (accessFlag == 0) { throw new ParseException("Incorrect class access modifier ["+strippedToken+"]"); } accessFlags |= accessFlag; } } } if (type != null && (type.startsWith("!") ^ set)) { int accessFlag = type.equals("class") ? 0 : type.equals( JavaAccessConstants.INTERFACE) || type.equals("!" + JavaAccessConstants.INTERFACE) ? AccessConstants.INTERFACE : type.equals( JavaAccessConstants.ENUM) || type.equals("!" + JavaAccessConstants.ENUM) ? AccessConstants.ENUM : -1; if (accessFlag == -1) { throw new ParseException("Incorrect class type ["+type+"]"); } accessFlags |= accessFlag; } return accessFlags; } /** * Creates a specification of class members, based on the given parameters. */ private MemberSpecification createMemberSpecification(boolean isMethod, boolean isConstructor, boolean allowValues, Map classSpecificationArgs) throws ParseException { // Extract the arguments. String access = (String)classSpecificationArgs.get("access"); String type = (String)classSpecificationArgs.get("type"); String annotation = (String)classSpecificationArgs.get("annotation"); String name = (String)classSpecificationArgs.get("name"); String parameters = (String)classSpecificationArgs.get("parameters"); String values = (String)classSpecificationArgs.get("value"); // Perform some basic conversions and checks on the attributes. if (annotation != null) { annotation = ClassUtil.internalType(annotation); } if (isMethod) { if (isConstructor) { if (type != null) { throw new ParseException("Type attribute not allowed in constructor specification ["+type+"]"); } if (parameters != null) { type = JavaTypeConstants.VOID; } if (values != null) { throw new ParseException("Values attribute not allowed in constructor specification ["+values+"]"); } name = ClassConstants.METHOD_NAME_INIT; } else if ((type != null) ^ (parameters != null)) { throw new ParseException("Type and parameters attributes must always be present in combination in method specification"); } } else { if (parameters != null) { throw new ParseException("Parameters attribute not allowed in field specification ["+parameters+"]"); } } if (values != null) { if (!allowValues) { throw new ParseException("Values attribute not allowed in this class specification ["+values+"]"); } if (type == null) { throw new ParseException("Values attribute must be specified in combination with type attribute in class member specification ["+values+"]"); } } List parameterList = ListUtil.commaSeparatedList(parameters); String descriptor = parameters != null ? ClassUtil.internalMethodDescriptor(type, parameterList) : type != null ? ClassUtil.internalType(type) : null; return values != null ? new MemberValueSpecification(requiredMemberAccessFlags(true, access), requiredMemberAccessFlags(false, access), annotation, name, descriptor, parseValues(type, ClassUtil.internalType(type), values)) : new MemberSpecification(requiredMemberAccessFlags(true, access), requiredMemberAccessFlags(false, access), annotation, name, descriptor); } /** * Parses the class member access flags that must be set (or not), based on * the given ProGuard-style flag specification. */ private int requiredMemberAccessFlags(boolean set, String access) throws ParseException { int accessFlags = 0; if (access != null) { StringTokenizer tokenizer = new StringTokenizer(access, " ,"); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.startsWith("!") ^ set) { String strippedToken = token.startsWith("!") ? token.substring(1) : token; int accessFlag = strippedToken.equals(JavaAccessConstants.PUBLIC) ? AccessConstants.PUBLIC : strippedToken.equals(JavaAccessConstants.PRIVATE) ? AccessConstants.PRIVATE : strippedToken.equals(JavaAccessConstants.PROTECTED) ? AccessConstants.PROTECTED : strippedToken.equals(JavaAccessConstants.STATIC) ? AccessConstants.STATIC : strippedToken.equals(JavaAccessConstants.FINAL) ? AccessConstants.FINAL : strippedToken.equals(JavaAccessConstants.SYNCHRONIZED) ? AccessConstants.SYNCHRONIZED : strippedToken.equals(JavaAccessConstants.VOLATILE) ? AccessConstants.VOLATILE : strippedToken.equals(JavaAccessConstants.TRANSIENT) ? AccessConstants.TRANSIENT : strippedToken.equals(JavaAccessConstants.BRIDGE) ? AccessConstants.BRIDGE : strippedToken.equals(JavaAccessConstants.VARARGS) ? AccessConstants.VARARGS : strippedToken.equals(JavaAccessConstants.NATIVE) ? AccessConstants.NATIVE : strippedToken.equals(JavaAccessConstants.ABSTRACT) ? AccessConstants.ABSTRACT : strippedToken.equals(JavaAccessConstants.STRICT) ? AccessConstants.STRICT : strippedToken.equals(JavaAccessConstants.SYNTHETIC) ? AccessConstants.SYNTHETIC : 0; if (accessFlag == 0) { throw new ParseException("Incorrect class member access modifier ["+strippedToken+"]"); } accessFlags |= accessFlag; } } } return accessFlags; } /** * Retrieves a specified boolean flag from the given map. */ private boolean retrieveBoolean(Map args, String name, boolean defaultValue) { if (args == null) { return defaultValue; } Object arg = args.get(name); return arg == null ? defaultValue : ((Boolean)arg).booleanValue(); } /** * Parses the given string as a value or value range of the given primitive * type. For example, values "123" or "100..199" of type "int" ("I"). */ private Number[] parseValues(String externalType, String internalType, String string) throws ParseException { int rangeIndex = string.lastIndexOf(".."); return rangeIndex >= 0 ? new Number[] { parseValue(externalType, internalType, string.substring(0, rangeIndex)), parseValue(externalType, internalType, string.substring(rangeIndex + 2)) } : new Number[] { parseValue(externalType, internalType, string) }; } /** * Parses the given string as a value of the given primitive type. * For example, value "123" of type "int" ("I"). * For example, value "true" of type "boolean" ("Z"), returned as 1. */ private Number parseValue(String externalType, String internalType, String string) throws ParseException { try { switch (internalType.charAt(0)) { case TypeConstants.BOOLEAN: { return parseBoolean(string); } case TypeConstants.BYTE: case TypeConstants.CHAR: case TypeConstants.SHORT: case TypeConstants.INT: { return Integer.decode(string); } //case TypeConstants.LONG: //{ // return Long.decode(string); //} //case TypeConstants.FLOAT: //{ // return Float.valueOf(string); //} //case TypeConstants.DOUBLE: //{ // return Double.valueOf(string); //} default: { throw new ParseException("Can't handle '"+externalType+"' constant ["+string+"]"); } } } catch (NumberFormatException e) { throw new ParseException("Can't parse "+externalType+" constant ["+string+"]"); } } /** * Parses the given boolean string as an integer (0 or 1). */ private Integer parseBoolean(String string) throws ParseException { if ("false".equals(string)) { return Integer.valueOf(0); } else if ("true".equals(string)) { return Integer.valueOf(1); } else { throw new ParseException("Unknown boolean constant ["+string+"]"); } } /** * Adds the given class specification to the given list, creating a new list * if necessary. */ protected List extendClassSpecifications(List classSpecifications, ClassSpecification classSpecification) { if (classSpecifications == null) { classSpecifications = new ArrayList(); } classSpecifications.add(classSpecification); return classSpecifications; } /** * Adds the given class specifications to the given list, creating a new * list if necessary. */ private List extendClassSpecifications(List classSpecifications, List additionalClassSpecifications) { if (additionalClassSpecifications != null) { if (classSpecifications == null) { classSpecifications = new ArrayList(); } classSpecifications.addAll(additionalClassSpecifications); } return classSpecifications; } /** * Adds the given filter to the given list of filter lists, creating a * new list if necessary. */ private List extendFilters(List filters, String filterString) { return extendFilters(filters, filterString, false); } /** * Adds the given filter to the given list of filter lists, creating a * new list if necessary. External class names are converted to internal * class names, if requested. */ private List extendFilters(List filters, String filterString, boolean convertExternalClassNames) { if (filters == null) { filters = new ArrayList(); } filters.add(extendFilter(null, filterString, convertExternalClassNames)); return filters; } /** * Adds the given filter to the given list, creating a new list if * necessary. */ private List extendFilter(List filter, String filterString) { return extendFilter(filter, filterString, false); } /** * Adds the given filter to the given list, creating a new list if * necessary. External class names are converted to internal class names, * if requested. */ private List extendFilter(List filter, String filterString, boolean convertExternalClassNames) { if (filter == null) { filter = new ArrayList(); } if (filterString == null) { // Clear the filter to keep all names. filter.clear(); } else { if (convertExternalClassNames) { filterString = ClassUtil.internalClassName(filterString); } // Append the filter. filter.addAll(ListUtil.commaSeparatedList(filterString)); } return filter; } /** * Adds the given object to the given list, creating a new list if * necessary. */ private List extendList(List list, Object object) { if (list == null) { list = new ArrayList(); } // Append to the list. list.add(object); return list; } /** * Converts a file object into a URL, looking relatively to this class if * necessary. */ private URL url(Object fileObject) throws MalformedURLException { File file = getProject().file(fileObject); return fileObject instanceof URL ? (URL)fileObject : fileObject instanceof String && !file.exists() ? ProGuardTask.class.getResource((String)fileObject) : file.toURI().toURL(); } /** * Returns null if the file does not represent a real file. Returns the file otherwise. */ private static File optionalFile(File input) { if (input == null || input == Configuration.STD_OUT) { return null; } return input; } } ================================================ FILE: gradle-plugin/src/main/kotlin/proguard/gradle/plugin/ProGuardPlugin.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.gradle.plugin import com.android.build.gradle.BaseExtension import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project import proguard.gradle.plugin.android.AndroidPlugin import proguard.gradle.plugin.android.agpVersion class ProGuardPlugin : Plugin { override fun apply(project: Project) { val androidExtension: BaseExtension? = project.extensions.findByName("android") as BaseExtension? val javaExtension = project.extensions.findByName("java") val javaErrMessage = """For Java projects, you can manually declare a ProGuardTask instead of applying the plugin: | | task myProguardTask(type: proguard.gradle.ProGuardTask) { | // ... | }""".trimMargin() when { androidExtension != null -> { if (agpVersion.majorVersion < 4) { throw GradleException( """The ProGuard plugin only supports Android plugin 4 and higher. |For Android plugin version 3 and lower, you can use ProGuard through the Android plugin integration. |Please refer to the manual for further details: https://www.guardsquare.com/manual/setup/gradleplugin """.trimMargin()) } AndroidPlugin(androidExtension).apply(project) } javaExtension != null -> { throw GradleException( """The ProGuard plugin requires the Android plugin to function properly. |$javaErrMessage """.trimMargin()) } else -> { throw GradleException( """For Android applications or libraries 'com.android.application' or 'com.android.library' is required, respectively. |$javaErrMessage """.trimMargin()) } } } } ================================================ FILE: gradle-plugin/src/main/kotlin/proguard/gradle/plugin/android/AndroidPlugin.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.gradle.plugin.android import com.android.build.api.variant.VariantInfo import com.android.build.gradle.AppExtension import com.android.build.gradle.BaseExtension import com.android.build.gradle.LibraryExtension import com.android.build.gradle.api.BaseVariant import com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask import com.android.build.gradle.internal.tasks.factory.dependsOn import com.github.zafarkhaja.semver.Version import java.io.File import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Configuration import org.gradle.api.attributes.Attribute import org.gradle.api.tasks.TaskProvider import proguard.gradle.plugin.android.AndroidProjectType.ANDROID_APPLICATION import proguard.gradle.plugin.android.AndroidProjectType.ANDROID_LIBRARY import proguard.gradle.plugin.android.dsl.ProGuardAndroidExtension import proguard.gradle.plugin.android.dsl.ProGuardConfiguration import proguard.gradle.plugin.android.dsl.UserProGuardConfiguration import proguard.gradle.plugin.android.dsl.VariantConfiguration import proguard.gradle.plugin.android.tasks.CollectConsumerRulesTask import proguard.gradle.plugin.android.tasks.ConsumerRuleFilterEntry import proguard.gradle.plugin.android.tasks.PrepareProguardConfigDirectoryTask import proguard.gradle.plugin.android.transforms.AndroidConsumerRulesTransform import proguard.gradle.plugin.android.transforms.ArchiveConsumerRulesTransform class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin { override fun apply(project: Project) { val collectConsumerRulesTask = project.tasks.register(COLLECT_CONSUMER_RULES_TASK_NAME) registerDependencyTransforms(project) val proguardBlock = project.extensions.create("dProtect", ProGuardAndroidExtension::class.java, project) val projectType = when (androidExtension) { is AppExtension -> ANDROID_APPLICATION is LibraryExtension -> ANDROID_LIBRARY else -> throw GradleException("The ProGuard Gradle plugin can only be used on Android application and library projects") } configureAapt(project) warnOldProguardVersion(project) androidExtension.registerTransform( ProGuardTransform(project, proguardBlock, projectType, androidExtension), collectConsumerRulesTask) project.afterEvaluate { if (proguardBlock.configurations.isEmpty()) throw GradleException("There are no configured variants in the 'proguard' block") val matchedConfigurations = mutableListOf() when (androidExtension) { is AppExtension -> androidExtension.applicationVariants.all { applicationVariant -> setupVariant(proguardBlock, applicationVariant, collectConsumerRulesTask, project)?.let { matchedConfigurations.add(it) } } is LibraryExtension -> androidExtension.libraryVariants.all { libraryVariant -> setupVariant(proguardBlock, libraryVariant, collectConsumerRulesTask, project)?.let { matchedConfigurations.add(it) } } } proguardBlock.configurations.forEach { checkConfigurationFile(project, it.configurations) } (proguardBlock.configurations - matchedConfigurations).apply { if (isNotEmpty()) when (size) { 1 -> throw GradleException("The configured variant '${first().name}' does not exist") else -> throw GradleException("The configured variants ${joinToString(separator = "', '", prefix = "'", postfix = "'") { it.name }} do not exist") } } } } private fun configureAapt(project: Project) { val createDirectoryTask = project.tasks.register("prepareProguardConfigDirectory", PrepareProguardConfigDirectoryTask::class.java) project.tasks.withType(LinkApplicationAndroidResourcesTask::class.java) { it.dependsOn(createDirectoryTask) } if (!androidExtension.aaptAdditionalParameters.contains("--proguard")) { androidExtension.aaptAdditionalParameters.addAll(listOf( "--proguard", project.buildDir.resolve("intermediates/proguard/configs/aapt_rules.pro").absolutePath) ) } if (!androidExtension.aaptAdditionalParameters.contains("--proguard-conditional-keep-rules")) { androidExtension.aaptAdditionalParameters.add("--proguard-conditional-keep-rules") } } private fun setupVariant(proguardBlock: ProGuardAndroidExtension, variant: BaseVariant, collectConsumerRulesTask: TaskProvider, project: Project): VariantConfiguration? { val matchingConfiguration = proguardBlock.configurations.findVariantConfiguration(variant.name) if (matchingConfiguration != null) { verifyNotMinified(variant) disableAaptOutputCaching(project, variant) collectConsumerRulesTask.dependsOn(createCollectConsumerRulesTask( project, variant, createConsumerRulesConfiguration(project, variant), matchingConfiguration.consumerRuleFilter, project.buildDir.resolve("intermediates/proguard/configs"))) } return matchingConfiguration } private fun createCollectConsumerRulesTask( project: Project, variant: BaseVariant, inputConfiguration: Configuration, consumerRuleFilter: MutableList, outputDir: File ): TaskProvider { fun parseConsumerRuleFilter(consumerRuleFilter: List) = consumerRuleFilter.map { filter -> val splits = filter.split(':') if (splits.size != 2) { throw GradleException("Invalid consumer rule filter entry: ${filter}\nExpected an entry of the form: :") } ConsumerRuleFilterEntry(splits[0], splits[1]) } return project.tasks.register(COLLECT_CONSUMER_RULES_TASK_NAME + variant.name.capitalize(), CollectConsumerRulesTask::class.java) { it.consumerRulesConfiguration = inputConfiguration it.consumerRuleFilter = parseConsumerRuleFilter(consumerRuleFilter) it.outputFile = File(File(outputDir, variant.dirName), CONSUMER_RULES_PRO) it.dependsOn(inputConfiguration.buildDependencies) } } private fun createConsumerRulesConfiguration(project: Project, variant: BaseVariant): Configuration = project.configurations.create("${variant.name}ProGuardConsumerRulesArtifacts") { it.isCanBeResolved = true it.isCanBeConsumed = false it.isTransitive = true it.extendsFrom(variant.runtimeConfiguration) copyConfigurationAttributes(it, variant.runtimeConfiguration) it.attributes.attribute(ATTRIBUTE_ARTIFACT_TYPE, ARTIFACT_TYPE_CONSUMER_RULES) } private fun checkConfigurationFile(project: Project, files: List) { files.filterIsInstance().forEach { val file = project.file(it.path) if (!file.exists()) throw GradleException("ProGuard configuration file ${file.absolutePath} was set but does not exist.") } } private fun verifyNotMinified(variant: BaseVariant) { if (variant.buildType.isMinifyEnabled) { throw GradleException( "The option 'minifyEnabled' is set to 'true' for variant '${variant.name}', but should be 'false' for variants processed by ProGuard") } } private fun copyConfigurationAttributes(destConfiguration: Configuration, srcConfiguration: Configuration) { srcConfiguration.attributes.keySet().forEach { attribute -> val attributeValue = srcConfiguration.attributes.getAttribute(attribute) destConfiguration.attributes.attribute(attribute as Attribute, attributeValue) } } private fun registerDependencyTransforms(project: Project) { project.dependencies.registerTransform(ArchiveConsumerRulesTransform::class.java) { it.from.attribute(ATTRIBUTE_ARTIFACT_TYPE, "aar") it.to.attribute(ATTRIBUTE_ARTIFACT_TYPE, ARTIFACT_TYPE_CONSUMER_RULES) } project.dependencies.registerTransform(ArchiveConsumerRulesTransform::class.java) { it.from.attribute(ATTRIBUTE_ARTIFACT_TYPE, "jar") it.to.attribute(ATTRIBUTE_ARTIFACT_TYPE, ARTIFACT_TYPE_CONSUMER_RULES) } project.dependencies.registerTransform(AndroidConsumerRulesTransform::class.java) { it.from.attribute(ATTRIBUTE_ARTIFACT_TYPE, "android-consumer-proguard-rules") it.to.attribute(ATTRIBUTE_ARTIFACT_TYPE, ARTIFACT_TYPE_CONSUMER_RULES) } } // TODO: improve loading AAPT rules so that we don't rely on this private fun disableAaptOutputCaching(project: Project, variant: BaseVariant) { val cachingEnabled = project.hasProperty("org.gradle.caching") && (project.findProperty("org.gradle.caching") as String).toBoolean() if (cachingEnabled) { // ensure that the aapt_rules.pro has been generated, so ProGuard can use it val processResourcesTask = project.tasks.findByName("process${variant.name.capitalize()}Resources") processResourcesTask?.outputs?.doNotCacheIf("We need to regenerate the aapt_rules.pro file, sorry!") { project.logger.debug("Disabling AAPT caching for ${variant.name}") !project.buildDir.resolve("intermediates/proguard/configs/aapt_rules.pro").exists() } } } private fun warnOldProguardVersion(project: Project) { if (agpVersion.majorVersion >= 7) return val message = """An older version of ProGuard has been detected on the classpath which can clash with ProGuard Gradle Plugin. This is likely due to a transitive dependency introduced by Android Gradle plugin. Please update your configuration to exclude the old version of ProGuard, for example: buildscript { // ... dependencies { // ... classpath("com.android.tools.build:gradle:x.y.z") { exclude group: "net.sf.proguard", module: "proguard-gradle" // or for kotlin (build.gradle.kts): // exclude(group = "net.sf.proguard", module = "proguard-gradle") } } }""" val proguardTask = Class.forName("proguard.gradle.ProGuardTask") // This method does not exist in the ProGuard version distributed with AGP. // It's used by `ProGuardTransform`, so throw an exception if it doesn't exist. if (proguardTask.methods.count { it.name == "extraJar" } == 0) { throw GradleException(message) } // Otherwise, only print a warning since it may or may not cause a problem project.rootProject.buildscript.configurations.all { it.resolvedConfiguration.resolvedArtifacts.find { it.moduleVersion.id.module.group.equals("net.sf.proguard") && it.moduleVersion.id.module.name.equals("proguard-gradle") }?.let { project.logger.warn(message) } } } companion object { const val COLLECT_CONSUMER_RULES_TASK_NAME = "collectConsumerRules" private const val CONSUMER_RULES_PRO = "consumer-rules.pro" private const val ARTIFACT_TYPE_CONSUMER_RULES = "proguard-consumer-rules" private val ATTRIBUTE_ARTIFACT_TYPE = Attribute.of("artifactType", String::class.java) } } enum class AndroidProjectType { ANDROID_APPLICATION, ANDROID_LIBRARY; } fun Iterable.findVariantConfiguration(variant: VariantInfo) = find { it.name == variant.fullVariantName } ?: find { it.name == variant.buildTypeName } fun Iterable.findVariantConfiguration(variantName: String) = find { it.name == variantName } ?: find { variantName.endsWith(it.name.capitalize()) } fun Iterable.hasVariantConfiguration(variantName: String) = this.findVariantConfiguration(variantName) != null val agpVersion: Version = Version.valueOf(com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION) /** * Extension property that wraps the aapt additional parameters, to take into account * API changes. */ @Suppress("UNCHECKED_CAST") val BaseExtension.aaptAdditionalParameters: MutableCollection get() { val aaptOptionsGetter = if (agpVersion.majorVersion >= 7) "getAndroidResources" else "getAaptOptions" val aaptOptions = this.javaClass.methods.first { it.name == aaptOptionsGetter }.invoke(this) val additionalParameters = aaptOptions.javaClass.methods.first { it.name == "getAdditionalParameters" }.invoke(aaptOptions) return if (additionalParameters != null) { additionalParameters as MutableCollection } else { // additionalParameters may be null because AGP 4.0.0 does not set a default empty list val newAdditionalParameters = ArrayList() aaptOptions.javaClass.methods.first { it.name == "setAdditionalParameters" }.invoke(aaptOptions, newAdditionalParameters) newAdditionalParameters } } ================================================ FILE: gradle-plugin/src/main/kotlin/proguard/gradle/plugin/android/ProGuardTransform.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.gradle.plugin.android import com.android.build.api.transform.Format import com.android.build.api.transform.Format.DIRECTORY import com.android.build.api.transform.Format.JAR import com.android.build.api.transform.QualifiedContent import com.android.build.api.transform.QualifiedContent.DefaultContentType import com.android.build.api.transform.QualifiedContent.DefaultContentType.CLASSES import com.android.build.api.transform.QualifiedContent.DefaultContentType.RESOURCES import com.android.build.api.transform.QualifiedContent.Scope import com.android.build.api.transform.QualifiedContent.Scope.EXTERNAL_LIBRARIES import com.android.build.api.transform.QualifiedContent.Scope.PROJECT import com.android.build.api.transform.QualifiedContent.Scope.PROVIDED_ONLY import com.android.build.api.transform.QualifiedContent.Scope.SUB_PROJECTS import com.android.build.api.transform.SecondaryFile import com.android.build.api.transform.Transform import com.android.build.api.transform.TransformInput import com.android.build.api.transform.TransformInvocation import com.android.build.api.transform.TransformOutputProvider import com.android.build.api.variant.VariantInfo import com.android.build.gradle.BaseExtension import java.io.File import org.gradle.api.Project import proguard.gradle.ProGuardTask import proguard.gradle.plugin.android.AndroidPlugin.Companion.COLLECT_CONSUMER_RULES_TASK_NAME import proguard.gradle.plugin.android.AndroidProjectType.ANDROID_APPLICATION import proguard.gradle.plugin.android.AndroidProjectType.ANDROID_LIBRARY import proguard.gradle.plugin.android.dsl.ProGuardAndroidExtension import proguard.gradle.plugin.android.dsl.UserProGuardConfiguration class ProGuardTransform( private val project: Project, private val proguardBlock: ProGuardAndroidExtension, private val projectType: AndroidProjectType, private val androidExtension: BaseExtension ) : Transform() { override fun transform(transformInvocation: TransformInvocation) { val variantName: String = transformInvocation.context.variantName val variantBlock = proguardBlock.configurations.findVariantConfiguration(variantName) ?: throw RuntimeException("Invalid configuration: $variantName") val proguardTask = project.tasks.create("proguardTask${variantName.capitalize()}", ProGuardTask::class.java) createIOEntries(transformInvocation.inputs, transformInvocation.outputProvider).forEach { proguardTask.injars(it.first) proguardTask.outjars(it.second) } proguardTask.extraJar(transformInvocation .outputProvider .getContentLocation("extra.jar", setOf(CLASSES, RESOURCES), mutableSetOf(PROJECT), JAR)) proguardTask.libraryjars(createLibraryJars(transformInvocation.referencedInputs)) proguardTask.configuration(project.tasks.getByPath(COLLECT_CONSUMER_RULES_TASK_NAME + variantName.capitalize()).outputs.files) proguardTask.configuration(variantBlock.configurations.map { project.file(it.path) }) val aaptRulesFile = getAaptRulesFile() if (aaptRulesFile != null && File(aaptRulesFile).exists()) { proguardTask.configuration(aaptRulesFile) } else { project.logger.warn("AAPT rules file not found: you may need to apply some extra keep rules for classes referenced from resources in your own ProGuard configuration.") } val mappingDir = project.buildDir.resolve("outputs/proguard/$variantName/mapping") if (!mappingDir.exists()) mappingDir.mkdirs() proguardTask.printmapping(File(mappingDir, "mapping.txt")) proguardTask.printseeds(File(mappingDir, "seeds.txt")) proguardTask.printusage(File(mappingDir, "usage.txt")) proguardTask.android() proguardTask.proguard() } override fun getName(): String = "ProguardTransform" override fun getInputTypes(): Set = setOf(CLASSES, RESOURCES) override fun getScopes(): MutableSet = when (projectType) { ANDROID_APPLICATION -> mutableSetOf(PROJECT, SUB_PROJECTS, EXTERNAL_LIBRARIES) ANDROID_LIBRARY -> mutableSetOf(PROJECT) } override fun getReferencedScopes(): MutableSet = when (projectType) { ANDROID_APPLICATION -> mutableSetOf(PROVIDED_ONLY) ANDROID_LIBRARY -> mutableSetOf(PROVIDED_ONLY, EXTERNAL_LIBRARIES, SUB_PROJECTS) } override fun isIncremental(): Boolean = false override fun applyToVariant(variant: VariantInfo?): Boolean = variant?.let { proguardBlock.configurations.findVariantConfiguration(it) } != null override fun getSecondaryFiles(): MutableCollection = proguardBlock .configurations .flatMap { it.configurations } .filterIsInstance() .map { SecondaryFile(project.file(it.path), false) } .toMutableSet().apply { getAaptRulesFile()?.let { this.add(SecondaryFile(project.file(it), false)) } } private fun createIOEntries( inputs: Collection, outputProvider: TransformOutputProvider ): List { fun createEntry(input: QualifiedContent, format: Format): ProGuardIOEntry { return ProGuardIOEntry( input.file, outputProvider.getContentLocation(input.name, input.contentTypes, input.scopes, format).canonicalFile) } return inputs.flatMap { input -> input.directoryInputs.map { createEntry(it, DIRECTORY) } + input.jarInputs.map { createEntry(it, JAR) } } } private fun createLibraryJars(inputs: Collection): List = inputs.flatMap { input -> input.directoryInputs.map { it.file } + input.jarInputs.map { it.file } } + listOf(androidExtension.sdkDirectory.resolve("platforms/${androidExtension.compileSdkVersion}/android.jar")) + androidExtension.libraryRequests.map { androidExtension.sdkDirectory.resolve("platforms/${androidExtension.compileSdkVersion}/optional/${it.name}.jar") } private fun getAaptRulesFile() = androidExtension.aaptAdditionalParameters .zipWithNext { cmd, param -> if (cmd == "--proguard") param else null } .filterNotNull() .firstOrNull { File(it).exists() } } typealias ProGuardIOEntry = Pair ================================================ FILE: gradle-plugin/src/main/kotlin/proguard/gradle/plugin/android/dsl/ProGuardAndroidExtension.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.gradle.plugin.android.dsl import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project open class ProGuardAndroidExtension(project: Project) { val configurations: NamedDomainObjectContainer = project.container(VariantConfiguration::class.java) } ================================================ FILE: gradle-plugin/src/main/kotlin/proguard/gradle/plugin/android/dsl/ProGuardConfiguration.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.gradle.plugin.android.dsl import proguard.gradle.ProGuardTask.DEFAULT_CONFIG_RESOURCE_PREFIX sealed class ProGuardConfiguration(val filename: String) { open val path: String = filename override fun toString(): String = filename } class UserProGuardConfiguration(filename: String) : ProGuardConfiguration(filename) class DefaultProGuardConfiguration private constructor(filename: String) : ProGuardConfiguration(filename) { override val path: String get() = "$DEFAULT_CONFIG_RESOURCE_PREFIX/$filename" companion object { private val ANDROID_DEBUG = DefaultProGuardConfiguration("proguard-android-debug.txt") private val ANDROID_RELEASE = DefaultProGuardConfiguration("proguard-android.txt") private val ANDROID_RELEASE_OPTIMIZE = DefaultProGuardConfiguration("proguard-android-optimize.txt") fun fromString(filename: String): ProGuardConfiguration { return when (filename) { ANDROID_DEBUG.filename -> ANDROID_DEBUG ANDROID_RELEASE.filename -> ANDROID_RELEASE ANDROID_RELEASE_OPTIMIZE.filename -> ANDROID_RELEASE_OPTIMIZE else -> throw IllegalArgumentException(""" The default ProGuard configuration '$filename' is invalid. Choose from: $ANDROID_DEBUG, $ANDROID_RELEASE, $ANDROID_RELEASE_OPTIMIZE """.trimIndent()) } } } } ================================================ FILE: gradle-plugin/src/main/kotlin/proguard/gradle/plugin/android/dsl/VariantConfiguration.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.gradle.plugin.android.dsl class VariantConfiguration(val name: String) { var configurations = mutableListOf() var consumerRuleFilter = mutableListOf() fun configurations(vararg configs: String) { configs.forEach { configuration(it) } } fun configuration(config: String) { configurations.add(UserProGuardConfiguration(config)) } fun defaultConfigurations(vararg configs: String) { configs.forEach { defaultConfiguration(it) } } fun defaultConfiguration(config: String) { configurations.add(DefaultProGuardConfiguration.fromString(config)) } fun consumerRuleFilter(vararg filters: String) { consumerRuleFilter.addAll(filters) } } ================================================ FILE: gradle-plugin/src/main/kotlin/proguard/gradle/plugin/android/tasks/CollectConsumerRulesTask.kt ================================================ package proguard.gradle.plugin.android.tasks import java.io.File import java.io.FileOutputStream import java.io.Serializable import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.ResolvedArtifact import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction open class CollectConsumerRulesTask : DefaultTask() { @get:InputFiles lateinit var consumerRulesConfiguration: Configuration @get:OutputFile lateinit var outputFile: File @get:Input var consumerRuleFilter: List = emptyList() @Internal val consumerRuleIgnoreFilter: List = listOf( // T13715: androidx.window contains an invalid -keep rule, the corrected rule // has been copied to the `proguard-android-common.txt` default configuration. ConsumerRuleFilterEntry("androidx.window", "window") ) @TaskAction fun extractConsumerRules() { if (outputFile.exists()) { outputFile.delete() } logger.debug("Writing out consumer rules to '$outputFile'...") outputFile.createNewFile() val matchedConsumerRuleFilterEntries = mutableListOf() val resolvedArtifacts = consumerRulesConfiguration.resolvedConfiguration.resolvedArtifacts for (artifact in resolvedArtifacts) { val entry = ConsumerRuleFilterEntry(artifact.moduleVersion.id.group, artifact.moduleVersion.id.name) val inConsumerRuleFilter = entry in consumerRuleFilter if (inConsumerRuleFilter || entry in consumerRuleIgnoreFilter) { logger.debug("Skipping consumer rules of '${artifact.file}'...") if (inConsumerRuleFilter) matchedConsumerRuleFilterEntries += entry continue } project.fileTree(artifact.file).forEach { file -> val relativeFilePath = file.absolutePath.removePrefix(artifact.file.absolutePath) file.inputStream().bufferedReader().use { reader -> FileOutputStream(outputFile, true).bufferedWriter().use { writer -> writer.append("# Consumer rules for ${artifact.moduleVersion.id} (${artifact.file.name}$relativeFilePath)") writer.newLine() writer.newLine() writer.append(reader.readText()) writer.newLine() writer.newLine() } } } } val fileDependencies = consumerRulesConfiguration.files - resolvedArtifacts.map(ResolvedArtifact::getFile) if (fileDependencies.isNotEmpty()) { FileOutputStream(outputFile, true).bufferedWriter().use { writer -> writer.append("# File dependencies") writer.newLine() writer.newLine() } } fileDependencies.forEach { fileDependency -> project.fileTree(fileDependency).forEach { file -> val relativeFilePath = file.absolutePath.removePrefix(fileDependency.absolutePath) file.inputStream().bufferedReader().use { reader -> FileOutputStream(outputFile, true).bufferedWriter().use { writer -> writer.append("# Consumer rules for ${fileDependency.name}$relativeFilePath") writer.newLine() writer.newLine() writer.append(reader.readText()) writer.newLine() writer.newLine() } } } } if (matchedConsumerRuleFilterEntries.size < consumerRuleFilter.size) { val notMatchedConsumerRuleFIlterEntries = consumerRuleFilter - matchedConsumerRuleFilterEntries throw GradleException("Consumer rule filter entr${if (notMatchedConsumerRuleFIlterEntries.size > 1) "ies" else "y"} '${notMatchedConsumerRuleFIlterEntries.joinToString { "${it.group}:${it.module}" }}' did not match any dependency") } } } data class ConsumerRuleFilterEntry(val group: String, val module: String) : Serializable ================================================ FILE: gradle-plugin/src/main/kotlin/proguard/gradle/plugin/android/tasks/PrepareProguardConfigDirectoryTask.kt ================================================ package proguard.gradle.plugin.android.tasks import org.gradle.api.DefaultTask import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction abstract class PrepareProguardConfigDirectoryTask : DefaultTask() { @OutputDirectory val file = project.buildDir.resolve("intermediates/proguard/configs") @TaskAction fun createDirectory() { file.mkdirs() } } ================================================ FILE: gradle-plugin/src/main/kotlin/proguard/gradle/plugin/android/transforms/AndroidConsumerRulesTransform.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2020 Guardsquare NV */ package proguard.gradle.plugin.android.transforms import java.io.File import org.gradle.api.artifacts.transform.InputArtifact import org.gradle.api.artifacts.transform.TransformAction import org.gradle.api.artifacts.transform.TransformOutputs import org.gradle.api.artifacts.transform.TransformParameters import org.gradle.api.file.FileSystemLocation import org.gradle.api.provider.Provider /** * Class that defines the transformation from 'android-consumer-proguard-rules' * artifacts to 'proguard-consumer-rule' artifacts. This is done by simply * copying the files over. */ abstract class AndroidConsumerRulesTransform : TransformAction { @get:InputArtifact abstract val inputArtifact: Provider override fun transform(outputs: TransformOutputs) { val inputFile = inputArtifact.get().asFile val outputDir = outputs.dir(inputFile.name) for (file in inputFile.listFiles() ?: emptyArray()) { val outputFile = File(outputDir, file.name) file.copyRecursively(outputFile) } } } ================================================ FILE: gradle-plugin/src/main/kotlin/proguard/gradle/plugin/android/transforms/ArchiveConsumerRulesTransform.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2020 Guardsquare NV */ package proguard.gradle.plugin.android.transforms import java.io.File import java.nio.file.FileSystems import java.util.zip.ZipFile import org.gradle.api.artifacts.transform.InputArtifact import org.gradle.api.artifacts.transform.TransformAction import org.gradle.api.artifacts.transform.TransformOutputs import org.gradle.api.artifacts.transform.TransformParameters import org.gradle.api.file.FileSystemLocation import org.gradle.api.provider.Provider /** * Class that defines the transformation from 'aar' and 'jar' artifacts to * 'proguard-consumer-rules' artifacts. This is done by extracting the consumer * rule files from the archive. */ abstract class ArchiveConsumerRulesTransform : TransformAction { @get:InputArtifact abstract val inputArtifact: Provider override fun transform(outputs: TransformOutputs) { val inputFile = inputArtifact.get().asFile val outputDir = outputs.dir(inputFile.name) val matchers = setOf( // Default location for consumer rules in aars injected by the // AGP and/or DexGuard. FileSystems.getDefault().getPathMatcher("glob:proguard.txt"), // Locations for consumer rules in jars. FileSystems.getDefault().getPathMatcher("glob:META-INF/proguard/*.pro") ) ZipFile(inputFile).use { zip -> zip.entries().asSequence().filter { entry -> matchers.any { it.matches(File(entry.name).toPath()) } }.forEach { entry -> zip.getInputStream(entry).use { input -> val outputFile = File(outputDir, entry.name) outputFile.parentFile.mkdirs() outputFile.outputStream().use { output -> input.copyTo(output) } } } } } } ================================================ FILE: gradle-plugin/src/main/resources/lib/proguard-android-common.txt ================================================ # Common ProGuard configuration for debug versions and release versions # of Android apps. # # Copyright (c) 2018-2021 Guardsquare NV -dontwarn sun.** -dontwarn javax.** -dontwarn java.awt.** -dontwarn java.nio.file.** -dontwarn org.apache.** -dontwarn build.IgnoreJava8API -dontwarn java.lang.invoke.** -android -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -verbose -keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod # From default AGP configuration. # Make sure that such classes are kept as they are # referenced from the AndroidManifest.xml or other # resource files. Some rules might be obsoleted by # aapt generated rules but keep them to be sure. -dontnote android.support.v4.app.Fragment -dontnote com.google.vending.licensing.ILicensingService -dontnote com.android.vending.licensing.ILicensingService -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgent -keep public class * extends android.preference.Preference -keep public class * extends android.support.v4.app.Fragment -keep public class * extends android.app.Fragment -keep public class com.google.vending.licensing.ILicensingService -keep public class com.android.vending.licensing.ILicensingService -keep public class com.google.android.vending.licensing.ILicensingService -dontnote com.android.vending.licensing.ILicensingService -dontnote com.google.vending.licensing.ILicensingService -dontnote com.google.android.vending.licensing.ILicensingService # From the default AGP config: keep constructors that are called from # the system via reflection. -keep public class * extends android.view.View { public (android.content.Context); public (android.content.Context, android.util.AttributeSet); public (android.content.Context, android.util.AttributeSet, int); public void set*(...); } -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet); } -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet, int); } # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native -keepclasseswithmembernames,includedescriptorclasses class * { native ; } # keep setters in Views so that animations can still work. # see http://proguard.sourceforge.net/manual/examples.html#beans -keepclassmembers public class * extends android.view.View { void set*(***); *** get*(); } # We want to keep methods in Activity that could be used in the XML attribute onClick. -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keepclassmembers class * implements android.os.Parcelable { public static final ** CREATOR; } # Preserve annotated Javascript interface methods. -keepclassmembers class * { @android.webkit.JavascriptInterface ; } # The support library contains references to newer platform versions. # Don't warn about those in case this app is linking against an older # platform version. We know about them, and they are safe. -dontnote android.support.** -dontnote androidx.** -dontwarn android.support.** -dontwarn androidx.** # This class is deprecated, but remains for backward compatibility. -dontwarn android.util.FloatMath # Understand the @Keep support annotation. -keep class android.support.annotation.Keep -keep class androidx.annotation.Keep -keep @android.support.annotation.Keep class * {*;} -keep @androidx.annotation.Keep class * {*;} -keepclasseswithmembers class * { @android.support.annotation.Keep ; } -keepclasseswithmembers class * { @androidx.annotation.Keep ; } -keepclasseswithmembers class * { @android.support.annotation.Keep ; } -keepclasseswithmembers class * { @androidx.annotation.Keep ; } -keepclasseswithmembers class * { @android.support.annotation.Keep (...); } -keepclasseswithmembers class * { @androidx.annotation.Keep (...); } # These classes are duplicated between android.jar and org.apache.http.legacy.jar. -dontnote org.apache.http.** -dontnote android.net.http.** # These classes are duplicated between android.jar and core-lambda-stubs.jar. -dontnote java.lang.invoke.** # T13715: This is a consumer rule from androidx.window where the original did not contain # return types causing a parsing error. -if class androidx.window.layout.SidecarCompat { public void setExtensionCallback(androidx.window.layout.ExtensionInterfaceCompat$ExtensionCallbackInterface); } -keep class androidx.window.layout.SidecarCompat$TranslatingCallback, androidx.window.layout.SidecarCompat$DistinctSidecarElementCallback { public void onDeviceStateChanged(androidx.window.sidecar.SidecarDeviceState); public void onWindowLayoutChanged(android.os.IBinder, androidx.window.sidecar.SidecarWindowLayoutInfo); } ================================================ FILE: gradle-plugin/src/main/resources/lib/proguard-android-debug.txt ================================================ # ProGuard configuration for debug versions of Android apps. # # Copyright (c) 2018-2021 Guardsquare NV -dontshrink -dontoptimize -dontobfuscate -include proguard-android-common.txt ================================================ FILE: gradle-plugin/src/main/resources/lib/proguard-android-optimize.txt ================================================ # ProGuard configuration for optimized release versions of Android apps. # # Copyright (c) 2018-2021 Guardsquare NV -include proguard-android-common.txt -optimizationpasses 5 ================================================ FILE: gradle-plugin/src/main/resources/lib/proguard-android.txt ================================================ # ProGuard configuration for release versions of Android apps. # # Copyright (c) 2018-2021 Guardsquare NV -dontoptimize -include proguard-android-optimize.txt ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/AgpVersionParserTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard.gradle import com.github.zafarkhaja.semver.Version import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe class AgpVersionParserTest : FreeSpec({ "Given a set of AGP versions" - { val agpVersions = listOf(Pair(4, "4.0.0-alpha01"), Pair(4, "4.1.0-beta01"), Pair(4, "4.2.0-rc01"), Pair(4, "4.0.0"), Pair(7, "7.1.1")) "When semver library is used to parse those versions" - { agpVersions.forEach { val result = Version.valueOf(it.second) "Then resulting majorVersion equals to the given input" { result.majorVersion shouldBe it.first } } } } }) ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/ConsumerRulesCollectionTest.kt ================================================ package proguard.gradle /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2020 Guardsquare NV */ import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.file.shouldExist import io.kotest.matchers.file.shouldNotExist import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldNotContain import java.io.File import org.gradle.testkit.runner.TaskOutcome import testutils.AndroidProject import testutils.applicationModule import testutils.createGradleRunner import testutils.createTestKitDir class ConsumerRulesCollectionTest : FreeSpec({ val testKitDir = createTestKitDir() "Given an Android project with one configured variant" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled false } debug { minifyEnabled false } } } proguard { configurations { release {} } } """.trimIndent())) }.create()) "When the tasks 'clean' and 'assembleDebug' are executed in a dry run" - { val result = createGradleRunner(project.rootDir, testKitDir, "--dry-run", "clean", "assembleDebug").build() "Then the 'collectConsumerRulesDebug' task would not be executed" { result.output.shouldNotContain("collectConsumerRulesDebug") } } "When the tasks 'clean' and 'assembleRelease' are executed in a dry run" - { val result = createGradleRunner(project.rootDir, testKitDir, "--dry-run", "clean", "assembleRelease").build() "Then the 'collectConsumerRulesRelease' task would be executed" { result.output.shouldContain("collectConsumerRulesRelease") } } "When the tasks 'clean' and 'collectConsumerRulesDebug' are executed " - { val result = createGradleRunner(project.rootDir, testKitDir, "clean", "collectConsumerRulesDebug").buildAndFail() "Then the task 'collectConsumerRulesDebug' is not executed" { result.task(":app:collectConsumerRulesDebug")?.outcome shouldBe null } "Then a subdirectory of the build directory should not contain the consumer rules" { File("${project.rootDir}/app/build/intermediates/proguard/configs/debug/consumer-rules.pro").shouldNotExist() } } "When the tasks 'clean' and 'collectConsumerRulesRelease' are executed " - { val result = createGradleRunner(project.rootDir, testKitDir, "clean", "collectConsumerRulesRelease").build() "Then the task 'collectConsumerRulesRelease' is successful" { result.task(":app:collectConsumerRulesRelease")?.outcome shouldBe TaskOutcome.SUCCESS } "Then a subdirectory of the build directory should contain the consumer rules" { File("${project.rootDir}/app/build/intermediates/proguard/configs/release/consumer-rules.pro").shouldExist() } } } }) ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/ConsumerRulesFilterTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 Guardsquare NV */ package proguard.gradle import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.file.shouldExist import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldNotContain import java.io.File import org.gradle.testkit.runner.TaskOutcome import testutils.AndroidProject import testutils.applicationModule import testutils.createGradleRunner import testutils.createTestKitDir class ConsumerRulesFilterTest : FreeSpec({ val testKitDir = createTestKitDir() "Given a project with a configuration block for a specific variant" - { val project = autoClose(AndroidProject().apply { addModule( applicationModule( "app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled false } } } proguard { configurations { release { consumerRuleFilter 'filter1', 'filter2' } } }""".trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir, "-si").build() "Then the build should succeed" { result.output shouldContain "BUILD SUCCESSFUL" } } } "Given an application project with a consumer rule filter" - { val project = autoClose(AndroidProject().apply { addModule( applicationModule( "app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } repositories { google() mavenCentral() } dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' } android { compileSdkVersion 29 defaultConfig { targetSdkVersion 30 minSdkVersion 14 versionCode 1 } buildTypes { release {} debug {} } } proguard { configurations { release { defaultConfiguration 'proguard-android.txt' consumerRuleFilter 'com.android.support:appcompat-v7' } } } """.trimIndent())) }.create()) "When the 'clean' and 'assemble' tasks are run" - { val result = createGradleRunner(project.rootDir, testKitDir, "clean", "assemble").build() "Then the tasks should run successfully" { result.task(":app:assemble")?.outcome shouldBe TaskOutcome.SUCCESS result.task(":app:assembleRelease")?.outcome shouldBe TaskOutcome.SUCCESS result.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS } "Then the release and debug apks are built" { File(project.rootDir, "app/build/outputs/apk/release/app-release-unsigned.apk").shouldExist() File(project.rootDir, "app/build/outputs/apk/debug/app-debug.apk").shouldExist() } "Then the excluded consumer rules are not in 'consumer-rules.pro'" { val consumerRuleFile = File(project.rootDir, "app/build/intermediates/proguard/configs/release/consumer-rules.pro") consumerRuleFile.shouldExist() consumerRuleFile.readText() shouldNotContain "com.android.support:appcompat-v7:28.0.0" } } "When the 'clean' and 'bundle' tasks are run" - { val result = createGradleRunner(project.rootDir, testKitDir, "clean", "bundle").build() "Then the tasks should run successfully" { result.task(":app:bundle")?.outcome shouldBe TaskOutcome.SUCCESS result.task(":app:bundleRelease")?.outcome shouldBe TaskOutcome.SUCCESS result.task(":app:bundleDebug")?.outcome shouldBe TaskOutcome.SUCCESS } "Then the release and debug aabs are built" { File(project.rootDir, "app/build/outputs/bundle/release/app-release.aab").shouldExist() File(project.rootDir, "app/build/outputs/bundle/debug/app-debug.aab").shouldExist() } "Then the excluded consumer rules are not in 'consumer-rules.pro'" { val consumerRuleFile = File(project.rootDir, "app/build/intermediates/proguard/configs/release/consumer-rules.pro") consumerRuleFile.shouldExist() consumerRuleFile.readText() shouldNotContain "com.android.support:appcompat-v7:28.0.0" } } } }) ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/GradlePluginIntegrationTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.gradle import io.kotest.core.spec.style.FreeSpec import io.kotest.core.spec.style.funSpec import io.kotest.engine.spec.tempdir import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import java.io.File import java.lang.management.ManagementFactory import org.apache.commons.io.FileUtils import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import testutils.TestPluginClasspath class GradlePluginIntegrationTest : FreeSpec({ "Gradle plugin can be applied to spring-boot sample" - { val projectRoot = tempdir() val fixture = File(GradlePluginIntegrationTest::class.java.classLoader.getResource("spring-boot").path) FileUtils.copyDirectory(fixture, projectRoot) TestPluginClasspath.applyToRootGradle(projectRoot) "When the build is executed" - { val result = GradleRunner.create() .forwardOutput() .withArguments("proguard") .withPluginClasspath() .withProjectDir(projectRoot) .build() "Then the build was successful" { result.output shouldContain "SUCCESSFUL" } } } // ProguardTask is still incompatible, but will not fail the build. Once the issue is resolved, this test will fail // and should be updated to demonstrate compatibility. "ProguardTask will not fail when configuration cache is used" - { val projectRoot = tempdir() val fixture = File(GradlePluginIntegrationTest::class.java.classLoader.getResource("application").path) FileUtils.copyDirectory(fixture, projectRoot) TestPluginClasspath.applyToRootGradle(projectRoot) "When the build is executed" - { val result = GradleRunner.create() .forwardOutput() .withArguments("proguard", "--configuration-cache") .withPluginClasspath() .withGradleVersion("7.4") .withProjectDir(projectRoot) // Ensure this value is true when `--debug-jvm` is passed to Gradle, and false otherwise .withDebug(ManagementFactory.getRuntimeMXBean().inputArguments.toString().indexOf("-agentlib:jdwp") > 0) .build() "Then the build was successful with configuration cache" { result.output shouldContain "SUCCESSFUL" // E.g., "Configuration cache entry discarded with 4 problems." result.output shouldContain "Configuration cache entry discarded with" } } } "gradle plugin can be configured via #configOption" - { include(testConfigOption("proguard")) include(testConfigOption("proguardWithConfigFile")) include(testConfigOption("proguardWithGeneratedConfigFile")) } }) fun testConfigOption(task: String) = funSpec { fun runBuild(projectRoot: File, vararg tasks: String): BuildResult? = GradleRunner.create() .forwardOutput() .withArguments(tasks.asList()) .withPluginClasspath() .withProjectDir(projectRoot) .build() fun succeeds(buildResult: BuildResult?, projectRoot: File, task: String, outcome: TaskOutcome = TaskOutcome.SUCCESS) { buildResult?.output shouldContain "SUCCESSFUL" buildResult?.task(":$task")?.outcome shouldBe outcome File(projectRoot, "build/$task-obfuscated.jar").isFile shouldBe true File(projectRoot, "build/$task-mapping.txt").isFile shouldBe true } val projectRoot = tempdir() val fixture = File(GradlePluginIntegrationTest::class.java.classLoader.getResource("gradle-kotlin-dsl").path) FileUtils.copyDirectory(fixture, projectRoot) TestPluginClasspath.applyToRootGradleKts(projectRoot) addConfigFileTasks(projectRoot) succeeds(runBuild(projectRoot, task), projectRoot, task) // When no inputs or outputs are modified, task should be UP-TO-DATE succeeds(runBuild(projectRoot, task), projectRoot, task, TaskOutcome.UP_TO_DATE) // When proguard outputs are subsequently deleted, task should re-execute runBuild(projectRoot, "clean") succeeds(runBuild(projectRoot, task), projectRoot, task) // When proguard input sources are modified, task should re-execute val srcFile = File(projectRoot, "src/main/java/gradlekotlindsl/App.java") srcFile.writeText(srcFile.readText().replace("Hello world.", "Howdy Earth.")) succeeds(runBuild(projectRoot, task), projectRoot, task) } /** * Add extra tasks that load from a separate configuration file, including one that is generated by another task. * This allows us to validate that up-to-date checks work regardless of the configuration mechanism. */ fun addConfigFileTasks(projectRoot: File) { val buildFile = File(projectRoot, "build.gradle.kts") buildFile.writeText(buildFile.readText() + """ tasks.register("proguardWithConfigFile") { dependsOn("jar") // Inputs and outputs declared in external config file are not automatically tracked inputs.file("build/libs/gradle-kotlin-dsl.jar") outputs.file("build/proguardWithConfigFile-obfuscated.jar") outputs.file("build/proguardWithConfigFile-mapping.txt") configuration("config.pro") } tasks.register("generateConfigFile") { val outputFile = file("build/proguard/config.pro") outputs.file(outputFile) doLast { file("build/proguard").mkdirs() outputFile.writeText("-basedirectory ../.. \n") outputFile.appendText(file("config.pro").readText().replace("proguardWithConfigFile", "proguardWithGeneratedConfigFile")) } } tasks.register("proguardWithGeneratedConfigFile") { dependsOn("jar") // Inputs and outputs declared in external config file are not automatically tracked inputs.file("build/libs/gradle-kotlin-dsl.jar") outputs.file("build/proguardWithGeneratedConfigFile-obfuscated.jar") outputs.file("build/proguardWithGeneratedConfigFile-mapping.txt") // Consume the "generateConfigFile" output. This will automatically add the task dependency. configuration(tasks.named("generateConfigFile")) } """.trimIndent()) val libraryJars = if (System.getProperty("java.version").startsWith("1.")) "/lib/rt.jar" else "/jmods/java.base.jmod(!**.jar;!module-info.class)" val configFile = File(projectRoot, "config.pro") configFile.createNewFile() configFile.writeText(""" -injars build/libs/gradle-kotlin-dsl.jar -outjars build/proguardWithConfigFile-obfuscated.jar -libraryjars $libraryJars -allowaccessmodification -repackageclasses -printmapping build/proguardWithConfigFile-mapping.txt -keep class gradlekotlindsl.App { public static void main(java.lang.String[]); } """.trimIndent()) } ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/ProGuardPluginLegacyAGPIntegrationTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2020 Guardsquare NV */ package proguard.gradle import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import org.gradle.testkit.runner.TaskOutcome import testutils.AndroidProject import testutils.applicationModule import testutils.createGradleRunner import testutils.createTestKitDir import testutils.libraryModule class ProGuardPluginLegacyAGPIntegrationTest : FreeSpec({ val testKitDir = createTestKitDir() "Given a project with the legacy integration" - { val project = autoClose(AndroidProject( gradleDotProperties = """ |android.enableR8=false |android.enableR8.libraries=false """.trimMargin(), buildDotGradle = """ buildscript { repositories { mavenCentral() // For anything else. google() // For the Android plugin. flatDir { dirs "${System.getProperty("local.repo")}" } } dependencies { classpath "com.android.tools.build:gradle:4.2.0" classpath ":proguard-gradle:${System.getProperty("proguard.version")}" } configurations.all { resolutionStrategy { dependencySubstitution { substitute module('net.sf.proguard:proguard-gradle') with module("com.guardsquare:proguard-gradle:${System.getProperty("proguard.version")}") } } } } allprojects { repositories { google() mavenCentral() } } """.trimIndent()).apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled true proguardFile getDefaultProguardFile('proguard-android.txt') } debug {} } } """.trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir, "clean", "assembleRelease", "--info").build() "Then the build should succeed" { result.output shouldContain "ProGuard, version ${System.getProperty("proguard.version")}" result.task(":app:assembleRelease")?.outcome shouldBe TaskOutcome.SUCCESS } } } "Given a library project with the legacy integration" - { val project = autoClose(AndroidProject( gradleDotProperties = """ |android.enableR8=false |android.enableR8.libraries=false |android.useAndroidX=true """.trimMargin(), buildDotGradle = """ buildscript { repositories { mavenCentral() // For anything else. google() // For the Android plugin. flatDir { dirs "${System.getProperty("local.repo")}" } } dependencies { classpath "com.android.tools.build:gradle:4.2.0" classpath ":proguard-gradle:${System.getProperty("proguard.version")}" } configurations.all { resolutionStrategy { dependencySubstitution { substitute module('net.sf.proguard:proguard-gradle') with module("com.guardsquare:proguard-gradle:${System.getProperty("proguard.version")}") } } } } allprojects { repositories { google() mavenCentral() } } """.trimIndent()).apply { addModule(libraryModule( "library", buildDotGradle = """ plugins { id 'com.android.library' } dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' } android { compileSdkVersion 29 defaultConfig { targetSdkVersion 29 minSdkVersion 14 } buildTypes { release { minifyEnabled true proguardFile getDefaultProguardFile('proguard-android.txt') } debug {} } } """)) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir, "clean", "assembleRelease", "--info").build() "Then the build should success" { result.output shouldContain "ProGuard, version ${System.getProperty("proguard.version")}" result.task(":library:assembleRelease")?.outcome shouldBe TaskOutcome.SUCCESS } } } }) ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/ProGuardPluginTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2020 Guardsquare NV */ package proguard.gradle import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import org.gradle.testkit.runner.TaskOutcome import testutils.AndroidProject import testutils.SourceFile import testutils.applicationModule import testutils.createGradleRunner import testutils.createTestKitDir import testutils.libraryModule class ProGuardPluginTest : FreeSpec({ val testKitDir = createTestKitDir() "Given a project without the Android Gradle plugin" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.guardsquare.proguard' } """.trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir).buildAndFail() "Then the build should fail" { result.output shouldContain "Failed to apply plugin 'com.guardsquare.proguard'" } } } "Given a project with an old Android Gradle plugin" - { val project = autoClose(AndroidProject(""" buildscript { repositories { mavenCentral() // For anything else. google() // For the Android plugin. flatDir { dirs "${System.getProperty("local.repo")}" } } dependencies { classpath "com.android.tools.build:gradle:3.6.3" classpath ":proguard-gradle:${System.getProperty("proguard.version")}" } } allprojects { repositories { google() mavenCentral() } } """.trimIndent()).apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release {} debug {} } } proguard { configurations { release {} } } """.trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir).buildAndFail() "Then the build should fail" { result.output shouldContain "The ProGuard plugin only supports Android plugin 4 and higher." } } } "Given a library project" - { val project = autoClose(AndroidProject().apply { addModule(libraryModule("lib", buildDotGradle = """ plugins { id 'com.android.library' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled false } debug {} } } proguard { configurations { release { defaultConfiguration 'proguard-android.txt' configuration 'proguard-project.txt' } } } """.trimIndent(), additionalFiles = listOf(SourceFile("proguard-project.txt", "-keep class **")))) }.create()) "When the project is assembled" - { val result = createGradleRunner(project.rootDir, testKitDir, "assembleRelease").build() "Then the build should succeed" { result.task(":lib:assembleRelease")?.outcome shouldBe TaskOutcome.SUCCESS } } } }) ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/ProguardCacheRelocateabilityIntegrationTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 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.gradle import io.kotest.core.spec.style.FreeSpec import io.kotest.engine.spec.tempdir import io.kotest.matchers.shouldBe import java.io.File import org.apache.commons.io.FileUtils import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import testutils.TestPluginClasspath class ProguardCacheRelocateabilityIntegrationTest : FreeSpec({ "proguard task can be relocated" - { val cacheDir = tempdir() val fixture = File(ProguardCacheRelocateabilityIntegrationTest::class.java.classLoader.getResource("spring-boot").path) val originalDir = tempdir() FileUtils.copyDirectory(fixture, originalDir) TestPluginClasspath.applyToRootGradle(originalDir) writeSettingsGradle(originalDir, cacheDir) val relocatedDir = tempdir() FileUtils.copyDirectory(fixture, relocatedDir) TestPluginClasspath.applyToRootGradle(relocatedDir) writeSettingsGradle(relocatedDir, cacheDir) GradleRunner.create() .forwardOutput() .withArguments("proguard", "--build-cache") .withPluginClasspath() .withProjectDir(originalDir) .build() val result2 = GradleRunner.create() .forwardOutput() .withArguments("proguard", "--build-cache") .withPluginClasspath() .withProjectDir(relocatedDir) .build() result2.task(":proguard")?.outcome shouldBe TaskOutcome.FROM_CACHE } }) fun writeSettingsGradle(projectDir: File, cacheDir: File) { File(projectDir, "settings.gradle").writeText(""" rootProject.name = 'demo' buildCache { local { directory = "${cacheDir.absolutePath.replace(File.separatorChar, '/')}" } } """.trimIndent()) } ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/plugin/android/dsl/AaptRulesTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.gradle.plugin.android.dsl import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.file.shouldExist import io.kotest.matchers.file.shouldNotExist import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import java.io.File import org.gradle.testkit.runner.TaskOutcome import testutils.AndroidProject import testutils.applicationModule import testutils.createGradleRunner import testutils.createTestKitDir class AaptRulesTest : FreeSpec({ val testKitDir = createTestKitDir() "Given a project with a configuration for a variant" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled false } } } proguard { configurations { debug { defaultConfiguration 'proguard-android-debug.txt' } } }""".trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir, "assembleDebug", "--info").build() val aaptRules = File("${project.moduleBuildDir("app")}/intermediates/proguard/configs/aapt_rules.pro") "The build should succeed" { result.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS } "The rules file should be passed to ProGuard" { result.output shouldContain "Loading configuration file ${aaptRules.canonicalPath}" } "The the AAPT rules should be generated" { aaptRules.shouldExist() aaptRules.readText() shouldContain "-keep class com.example.app.MainActivity { (); }" } } } "Given a project with a configuration for a variant and existing aaptOptions" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled false } } aaptOptions.additionalParameters = ["--proguard", (new File(rootDir, "test.pro")).absolutePath] } proguard { configurations { debug { defaultConfiguration 'proguard-android-debug.txt' } } }""".trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir, "assembleDebug", "--info").build() val aaptRules = File("${project.rootDir}/test.pro") "The build should succeed" { result.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS } "The rules file should be passed to ProGuard" { result.output shouldContain "Loading configuration file ${aaptRules.canonicalPath}" } "The AAPT rules file should be re-used" { aaptRules.shouldExist() aaptRules.readText() shouldContain "-keep class com.example.app.MainActivity { (); }" } "The AAPT rules file should not be generated" { val generatedAaptRules = File("${project.moduleBuildDir("app")}/intermediates/proguard/configs/aapt_rules.pro") generatedAaptRules.shouldNotExist() } } } "Given a project with a configuration for a variant in a project that has caching enabled" - { val project = autoClose(AndroidProject(gradleDotProperties = "org.gradle.caching=true").apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 defaultConfig { versionCode 1 } buildTypes { release { minifyEnabled false } } } proguard { configurations { debug { defaultConfiguration 'proguard-android-debug.txt' } } }""".trimIndent())) }.create()) "When assembleDebug is executed" - { val aaptRules = File("${project.moduleBuildDir("app")}/intermediates/proguard/configs/aapt_rules.pro") // run once, then delete the aapt_rules file val result0 = createGradleRunner(project.rootDir, testKitDir, "assembleDebug").build() aaptRules.delete() // running again should re-generate the rules file val result = createGradleRunner(project.rootDir, testKitDir, "clean", "assembleDebug", "--info").build() "The build should succeed" { result0.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS result.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS } "The rules file should be passed to ProGuard" { result.output shouldContain "Loading configuration file ${aaptRules.canonicalPath}" } "The the AAPT rules should be generated" { aaptRules.shouldExist() aaptRules.readText() shouldContain "-keep class com.example.app.MainActivity { (); }" } } "When bundleDebug is executed" - { val aaptRules = File("${project.moduleBuildDir("app")}/intermediates/proguard/configs/aapt_rules.pro") // run once, then delete the aapt_rules file val result0 = createGradleRunner(project.rootDir, testKitDir, "clean", "bundleDebug").build() aaptRules.delete() // running again should re-generate the rules file val result = createGradleRunner(project.rootDir, testKitDir, "clean", "bundleDebug", "--info").build() "The build should succeed" { result0.task(":app:bundleDebug")?.outcome shouldBe TaskOutcome.SUCCESS result.task(":app:bundleDebug")?.outcome shouldBe TaskOutcome.SUCCESS } "The rules file should be passed to ProGuard" { result.output shouldContain "Loading configuration file ${aaptRules.canonicalPath}" } "The the AAPT rules should be generated" { aaptRules.shouldExist() aaptRules.readText() shouldContain "-keep class com.example.app.MainActivity { (); }" } } } "Given a project with a flavor configuration for a variant in a project that has caching enabled" - { val project = autoClose(AndroidProject(gradleDotProperties = "org.gradle.caching=true").apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled false } } flavorDimensions "version" productFlavors { demo { dimension "version" applicationIdSuffix ".demo" versionNameSuffix "-demo" } full { dimension "version" applicationIdSuffix ".full" versionNameSuffix "-full" } } } proguard { configurations { demoDebug { defaultConfiguration 'proguard-android-debug.txt' } } }""".trimIndent())) }.create()) "When the project is evaluated" - { val aaptRules = File("${project.moduleBuildDir("app")}/intermediates/proguard/configs/aapt_rules.pro") // run once, then delete the aapt_rules file val result0 = createGradleRunner(project.rootDir, testKitDir, "assembleDebug").build() aaptRules.delete() // running again should re-generate the rules file val result = createGradleRunner(project.rootDir, testKitDir, "clean", "assembleDebug", "--info").build() "The build should succeed" { result0.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS result.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS } "The rules file should be passed to ProGuard" { result.output shouldContain "Loading configuration file ${aaptRules.canonicalPath}" } "The the AAPT rules should be generated" { aaptRules.shouldExist() aaptRules.readText() shouldContain "-keep class com.example.app.MainActivity { (); }" } } } }) ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/plugin/android/dsl/ConfigurationOrderTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.gradle.plugin.android.dsl import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.collections.shouldExistInOrder import testutils.AndroidProject import testutils.SourceFile import testutils.applicationModule import testutils.createGradleRunner import testutils.createTestKitDir class ConfigurationOrderTest : FreeSpec({ val testKitDir = createTestKitDir() "Given a project with multiple configuration files" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled false } } } proguard { configurations { release { configuration 'proguard-project1.txt' defaultConfiguration 'proguard-android.txt' configuration 'proguard-project2.txt' } } }""".trimIndent(), additionalFiles = listOf(SourceFile("proguard-project1.txt"), SourceFile("proguard-project2.txt")))) }.create()) "When the project is built" - { val result = createGradleRunner(project.rootDir, testKitDir, "assembleRelease", "--info").build() "The configurations should be included in order" { result.output.lines().shouldExistInOrder( { it.contains("proguard-project1.txt") }, { it.contains("proguard-android.txt") }, { it.contains("proguard-project2.txt") }) } } } }) ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/plugin/android/dsl/ConfigurationTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package proguard.gradle.plugin.android.dsl import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import org.gradle.testkit.runner.TaskOutcome import testutils.AndroidProject import testutils.applicationModule import testutils.createGradleRunner import testutils.createTestKitDir class ConfigurationTest : FreeSpec({ val testKitDir = createTestKitDir() "Given a project with a configuration block specifying a ProGuard configuration file that does not exist" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled false } } } proguard { configurations { release { configuration 'non-existing-file.txt' } } }""".trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir).buildAndFail() "Then the build should fail with an error message" { result.output shouldContain "ProGuard configuration file .*non-existing-file.txt was set but does not exist.".toRegex() } } } "Given a project with a configuration for a minified variant that is not configured" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled true } } } proguard { configurations { debug { defaultConfiguration 'proguard-android-debug.txt' } } }""".trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir, "assemble").build() "Then the build should succeed" { result.task(":app:assemble")?.outcome shouldBe TaskOutcome.SUCCESS } } } "Given a project with a configuration for a minified variant" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled true } } } proguard { configurations { release { defaultConfiguration 'proguard-android.txt' } } }""".trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir).buildAndFail() "Then the build should fail with an error message" { result.output shouldContain "The option 'minifyEnabled' is set to 'true' for variant 'release', but should be 'false' for variants processed by ProGuard" } } } "Given a project no configured variants" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled true } } } proguard { configurations { } }""".trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir).buildAndFail() "Then the build should fail with an error message" { result.output shouldContain "There are no configured variants in the 'proguard' block" } } } "Given a project configured with a variant that does not exist" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled true } } } proguard { configurations { foo { defaultConfiguration 'proguard-android.txt' } } }""".trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir).buildAndFail() "Then the build should fail with an error message" { result.output shouldContain "The configured variant 'foo' does not exist" } } } "Given a project configured with multiple variants that do not exist" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled true } } } proguard { configurations { foo { defaultConfiguration 'proguard-android.txt' } bar { defaultConfiguration 'proguard-android.txt' } } }""".trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir).buildAndFail() "Then the build should fail with an error message" { result.output shouldContain "The configured variants (('foo', 'bar')|('bar', 'foo')) do not exist".toRegex() } } } "Given a project configured with a flavor variant that does not exist" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 buildTypes { release { minifyEnabled true } } flavorDimensions "version" productFlavors { demo { dimension "version" applicationIdSuffix ".demo" versionNameSuffix "-demo" } full { dimension "version" applicationIdSuffix ".full" versionNameSuffix "-full" } } } proguard { configurations { fooRelease { defaultConfiguration 'proguard-android.txt' } } }""".trimIndent())) }.create()) "When the project is evaluated" - { val result = createGradleRunner(project.rootDir, testKitDir).buildAndFail() "Then the build should fail with an error message" { result.output shouldContain "The configured variant 'fooRelease' does not exist" } } } }) ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/plugin/android/dsl/DefaultConfigurationTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2020 Guardsquare NV */ package proguard.gradle.plugin.android.dsl import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.string.shouldContain import testutils.AndroidProject import testutils.applicationModule import testutils.createGradleRunner import testutils.createTestKitDir class DefaultConfigurationTest : FreeSpec({ val testKitDir = createTestKitDir() "Given a project with a configuration specifying a default configuration file that does not exist" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } buildTypes { release { minifyEnabled false } } } proguard { configurations { release { defaultConfiguration 'non-existing' } } } """.trimIndent())) }.create()) "When the project is evaluated" - { val buildResult = createGradleRunner(project.rootDir, testKitDir, "-si").buildAndFail() "Then an exception is thrown" { buildResult.output shouldContain "The default ProGuard configuration 'non-existing' is invalid" } } } }) ================================================ FILE: gradle-plugin/src/test/kotlin/proguard/gradle/plugin/android/dsl/FlavorsConfigurationTest.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2020 Guardsquare NV */ package proguard.gradle.plugin.android.dsl import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.file.shouldExist import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import java.io.File import org.gradle.testkit.runner.TaskOutcome.SUCCESS import testutils.AndroidProject import testutils.applicationModule import testutils.createGradleRunner import testutils.createTestKitDir class FlavorsConfigurationTest : FreeSpec({ val testKitDir = createTestKitDir() "Given a project with a configuration specifying flavours" - { val project = autoClose(AndroidProject().apply { addModule(applicationModule("app", buildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 30 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } buildTypes { debug { // Disable the built-in minification minifyEnabled false } release { // Disable the built-in minification minifyEnabled false } } flavorDimensions "version" productFlavors { demo { dimension "version" applicationIdSuffix ".demo" versionNameSuffix "-demo" } full { dimension "version" applicationIdSuffix ".full" versionNameSuffix "-full" } } } proguard { configurations { release { defaultConfiguration 'proguard-android-optimize.txt' } demoDebug { defaultConfiguration 'proguard-android-debug.txt' } } } """.trimIndent())) }.create()) "When the build is executed" - { val buildResult = createGradleRunner(project.rootDir, testKitDir, "assemble").build() "ProGuard should be executed for each matching build variant" { buildResult.task(":app:transformClassesAndResourcesWithProguardTransformForDemoRelease")?.outcome shouldBe SUCCESS buildResult.task(":app:transformClassesAndResourcesWithProguardTransformForFullRelease")?.outcome shouldBe SUCCESS buildResult.task(":app:transformClassesAndResourcesWithProguardTransformForDemoDebug")?.outcome shouldBe SUCCESS } "ProGuard should not be executed for non-matching build variants" { buildResult.task(":app:transformClassesAndResourcesWithProguardTransformForFullDebug")?.outcome shouldBe null } "AAPT rules should be generated" - { val aaptRules = File("${project.moduleBuildDir("app")}/intermediates/proguard/configs/aapt_rules.pro") aaptRules.shouldExist() aaptRules.readText() shouldContain "-keep class com.example.app.MainActivity { (); }" } } } }) ================================================ FILE: gradle-plugin/src/test/kotlin/testutils/AndroidProjectBuilder.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2021 Guardsquare NV */ package testutils import java.io.File import java.nio.file.Files.createTempDirectory import org.intellij.lang.annotations.Language interface ProjectFile { val path: String fun create(moduleDir: File) } class SourceFile(override val path: String, val source: String = "") : ProjectFile { override fun create(moduleDir: File) { val file = File(moduleDir, path) file.parentFile.mkdirs() file.writeText(source) } } class ResourceFile(override val path: String, private val resourceName: String) : ProjectFile { override fun create(moduleDir: File) { val file = File(moduleDir, path) file.parentFile.mkdirs() javaClass.getResourceAsStream(resourceName).use { input -> file.outputStream().use { output -> input.copyTo(output) } } } } data class Module(val name: String, val files: List) { val projectPath get() = ":$name" } fun androidModule( name: String, buildDotGradle: String, androidManifest: String, javaSources: Map, additionalFiles: List ) = Module(name, additionalFiles + listOf( SourceFile("build.gradle", buildDotGradle), SourceFile("src/main/AndroidManifest.xml", androidManifest)) + javaSources.map { (k, v) -> SourceFile("src/main/java/$k", v) }) fun libraryModule( name: String, @Language("Groovy") buildDotGradle: String = defaultBuildDotGradle(ProjectType.LIBRARY), @Language("xml") androidManifest: String = defaultAndroidLibraryManifest, javaSources: Map = defaultLibrarySources, additionalFiles: List = emptyList() ) = androidModule(name, buildDotGradle, androidManifest, javaSources, additionalFiles) fun applicationModule( name: String, @Language("Groovy") buildDotGradle: String = defaultBuildDotGradle(ProjectType.APPLICATION), @Language("xml") androidManifest: String = defaultAndroidApplicationManifest, javaSources: Map = defaultApplicationSources, additionalFiles: List = emptyList() ) = androidModule(name, buildDotGradle, androidManifest, javaSources, additionalFiles) fun baseFeatureModule( name: String, @Language("Groovy") buildDotGradle: String = defaultBaseFeatureBuildDotGradle, @Language("xml") androidManifest: String = defaultAndroidApplicationManifest, javaSources: Map = defaultApplicationSources, additionalFiles: List = defaultBaseFeatureAdditionalFiles ) = androidModule(name, buildDotGradle, androidManifest, javaSources, additionalFiles) fun dynamicFeatureModule( name: String, @Language("Groovy") buildDotGradle: String = defaultDynamicFeatureBuildDotGradle, @Language("xml") androidManifest: String = defaultDynamicFeatureManifest, javaSources: Map = defaultDynamicFeatureSources, additionalFiles: List = emptyList() ) = androidModule(name, buildDotGradle, androidManifest, javaSources, additionalFiles) fun jarModule( name: String, @Language("Groovy") buildDotGradle: String = defaultJarBuildDotGradle, javaSources: Map = defaultJarSources, additionalFiles: List = emptyList() ) = Module(name, additionalFiles + listOf( SourceFile("build.gradle", buildDotGradle)) + javaSources.map { (k, v) -> SourceFile("src/main/java/$k", v) }) class AndroidProject( @Language("Groovy") val buildDotGradle: String = defaultRootBuildDotGradle, @Language("Groovy") private val overrideSettingsDotGradle: String? = null, private val gradleDotProperties: String? = null ) : AutoCloseable { val rootDir: File = createTempDirectory("proguard-gradle").toFile() private val modules = mutableListOf() private val settingsDotGradle: String get() = overrideSettingsDotGradle ?: defaultSettingsDotGradle(modules) fun addModule(module: Module) { modules.add(module) } fun module(name: String): Module? = modules.find { it.name == name } fun moduleBuildDir(name: String): File? = with(module(name)) { if (this != null) File(rootDir, "${this.name}/build") else null } fun create(): AndroidProject { rootDir.mkdirs() File(rootDir, "build.gradle").writeText(buildDotGradle) File(rootDir, "settings.gradle").writeText(settingsDotGradle) if (gradleDotProperties != null) { File(rootDir, "gradle.properties").writeText(gradleDotProperties) } for (module in modules) { val moduleDir = File(rootDir, module.name) moduleDir.mkdirs() for (file in module.files) { file.create(moduleDir) } } return this } override fun close() { if (!rootDir.deleteRecursively()) throw Exception("Could not delete root dir '$rootDir'") } } fun defaultSettingsDotGradle(modules: List) = modules.joinToString(prefix = "include ") { "'${it.projectPath}'" } private val defaultRootBuildDotGradle = """ buildscript { repositories { mavenCentral() // For anything else. google() // For the Android plugin. flatDir { dirs "${System.getProperty("local.repo")}" } } dependencies { classpath "com.android.tools.build:gradle:${System.getProperty("agp.version")}" classpath ":proguard-gradle:${System.getProperty("proguard.version")}" } } allprojects { repositories { google() mavenCentral() } } """.trimIndent() enum class ProjectType(val plugin: String) { APPLICATION("com.android.application"), DYNAMIC_FEATURE("com.android.dynamic-feature"), LIBRARY("com.android.library") } private fun defaultBuildDotGradle(type: ProjectType) = """ plugins { id '${type.plugin}' id 'com.guardsquare.proguard' } android { compileSdkVersion 29 defaultConfig { targetSdkVersion 29 minSdkVersion 14 versionCode 1 } buildTypes { release {} debug {} } } """.trimIndent() private val defaultBaseFeatureBuildDotGradle = """ plugins { id 'com.android.application' id 'com.guardsquare.proguard' } android { compileSdkVersion 29 defaultConfig { targetSdkVersion 29 minSdkVersion 14 versionCode 1 } buildTypes { release {} debug {} } dynamicFeatures = [':feature'] } """.trimIndent() private val defaultDynamicFeatureBuildDotGradle = """ plugins { id 'com.android.dynamic-feature' id 'com.guardsquare.proguard' } android { compileSdkVersion 29 defaultConfig { targetSdkVersion 29 minSdkVersion 14 } buildTypes { release {} debug {} } } dependencies { implementation project(':app') } """.trimIndent() private val defaultJarBuildDotGradle = """ plugins { id 'java-library' } """.trimIndent() private val defaultAndroidApplicationManifest = """ """.trimIndent() private val defaultDynamicFeatureManifest = """ """.trimIndent() private val defaultAndroidLibraryManifest = """ """.trimIndent() private val defaultApplicationSources = mutableMapOf( "com/example/app/MainActivity.java" to """ package com.example.app; import android.app.Activity; import android.os.Bundle; import android.view.Gravity; import android.widget.*; public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView view = new TextView(this); view.setText("Hello World!"); view.setGravity(Gravity.CENTER); setContentView(view); } } """.trimIndent()) private val defaultDynamicFeatureSources = mutableMapOf( "com/example/feature/FeatureActivity.java" to """ package com.example.feature; import android.app.Activity; import android.os.Bundle; import android.view.Gravity; import android.widget.*; public class FeatureActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView view = new TextView(this); view.setText("Hello World!"); view.setGravity(Gravity.CENTER); setContentView(view); } } """.trimIndent()) private val defaultLibrarySources = mutableMapOf( "com/example/lib/LibraryClass.java" to """ package com.example.lib; public class LibraryClass { public String getMessage() { return "Hello World!"; } } """.trimIndent() ) private val defaultJarSources = mutableMapOf( "com/example/jar/JarClass.java" to """ package com.example.jar; public class JarClass { public String getMessage() { return "Hello World!"; } } """.trimIndent() ) val defaultBaseFeatureAdditionalFiles = listOf( SourceFile("src/main/res/values/module_names.xml", """ dynamic-feature """.trimIndent()) ) val defaultGoogleServicesResourceFiles = listOf( SourceFile("src/main/res/values/strings.xml", """ 1:260040693598:android:2009aac2b4e342544221cf """.trimIndent()) ) ================================================ FILE: gradle-plugin/src/test/kotlin/testutils/GradleRunnerUtil.kt ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2020 Guardsquare NV */ package testutils import java.io.File import java.nio.file.Files.createTempDirectory import org.gradle.testkit.runner.GradleRunner const val GRADLE_VERSION = "7.0" fun createGradleRunner(projectDir: File, testKitDir: File, vararg arguments: String): GradleRunner = GradleRunner.create() .withGradleVersion(GRADLE_VERSION) .withTestKitDir(testKitDir) .withProjectDir(projectDir) .withArguments(*arguments) fun createTestKitDir(): File { val testKitDir = createTempDirectory("testkit").toFile() val removeTestKitDirHook = Thread { testKitDir.deleteRecursively() } Runtime.getRuntime().addShutdownHook(removeTestKitDirHook) return testKitDir } ================================================ FILE: gradle-plugin/src/test/kotlin/testutils/TestPluginClasspath.kt ================================================ package testutils import java.io.File class TestPluginClasspath { companion object { /** * Replaces the buildscript block in the root build.gradle file with nothing and applies the proguard plugin * in the plugins block. * @param projectDir the root project dir where the build.gradle file is located */ fun applyToRootGradle(projectDir: File) { val buildGradle = File(projectDir, "build.gradle") buildGradle.writeText(buildGradle.readText() .replace(Regex("(?ms)buildscript\\s+\\{.*?^}"), "") .replace("id 'java'", "id 'java'\n id 'com.guardsquare.proguard' apply false")) } /** * Replaces the buildscript block in the root build.gradle.kts file with nothing and applies the proguard plugin * in the plugins block. * @param projectDir the root project dir where the build.gradle file is located */ fun applyToRootGradleKts(projectDir: File) { val buildGradle = File(projectDir, "build.gradle.kts") buildGradle.writeText(buildGradle.readText() .replace(Regex("(?ms)buildscript\\s+\\{.*?^}"), "") .replaceFirst("java", "java\n id(\"com.guardsquare.proguard\").apply(false)")) } } } ================================================ FILE: gradle.properties ================================================ proguardVersion = 7.3.3 dProtectVersion = 1.0.0 dprotectCoreVersion = 1.0.0 dprotectCoreCommit = latest # The version of ProGuardCORE that sub-projects are built with proguardCoreVersion = 9.0.8 gsonVersion = 2.9.0 kotlinVersion = 1.7.20 target = 1.8 # Optionally compile the WTK plugin. wtkDir = /usr/local/java/wtk2.1 wtkHome = /usr/local/java/wtk org.gradle.vfs.watch=true org.gradle.parallel=true org.gradle.caching=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # 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 # # https://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. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MSYS* | MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: gui/build.gradle ================================================ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { id 'com.github.johnrengelman.shadow' id 'java' id 'maven-publish' } repositories { mavenCentral() } sourceSets.main { java { srcDirs = ['src'] include '**/*.java' } resources { srcDirs = ['src'] include '**/*.properties' include '**/*.gif' include '**/*.png' include '**/*.pro' } } dependencies { implementation project(':base') implementation project(':retrace') implementation 'org.apache.logging.log4j:log4j-api:2.17.1' implementation 'org.apache.logging.log4j:log4j-core:2.17.1' } task fatJar(type: ShadowJar) { destinationDirectory.set(file("$rootDir/lib")) archiveFileName.set('proguardgui.jar') from sourceSets.main.output configurations = [project.configurations.runtimeClasspath] manifest { attributes( 'Manifest-Version': '1.0', 'Main-Class': 'proguard.gui.ProGuardGUI', 'Multi-Release': true, 'Implementation-Version': archiveVersion.get()) } } assemble.dependsOn fatJar afterEvaluate { publishing { publications.getByName(project.name) { pom { description = 'ProGuardGUI is an interface for ProGuard, the free shrinker, optimizer, obfuscator, and preverifier for Java bytecode' } } } } ================================================ FILE: gui/src/proguard/gui/ClassPathPanel.java ================================================ /* * 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.gui; import proguard.*; import proguard.util.ListUtil; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.File; import java.util.List; /** * This ListPanel allows the user to add, edit, filter, move, and * remove ClassPathEntry objects in a ClassPath object. * * @author Eric Lafortune */ class ClassPathPanel extends ListPanel { private final JFrame owner; private final boolean inputAndOutput; private final JFileChooser chooser; private final FilterDialog filterDialog; public ClassPathPanel(JFrame owner, boolean inputAndOutput) { super(); super.firstSelectionButton = inputAndOutput ? 3 : 2; this.owner = owner; this.inputAndOutput = inputAndOutput; list.setCellRenderer(new MyListCellRenderer()); chooser = new JFileChooser(""); chooser.setMultiSelectionEnabled(true); chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); chooser.addChoosableFileFilter( new ExtensionFileFilter(msg("jarExtensions"), new String[] { ".apk", ".ap_", ".jar", ".aar", ".war", ".ear", ".jmod", ".zip" })); chooser.setApproveButtonText(msg("ok")); filterDialog = new FilterDialog(owner, msg("enterFilter")); addAddButton(inputAndOutput, false); if (inputAndOutput) { addAddButton(inputAndOutput, true); } addEditButton(); addFilterButton(); addRemoveButton(); addUpButton(); addDownButton(); enableSelectionButtons(); } protected void addAddButton(boolean inputAndOutput, final boolean isOutput) { JButton addButton = new JButton(msg(inputAndOutput ? isOutput ? "addOutput" : "addInput" : "add")); addButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { chooser.setDialogTitle(msg("addJars")); chooser.setSelectedFile(null); chooser.setSelectedFiles(null); int returnValue = chooser.showOpenDialog(owner); if (returnValue == JFileChooser.APPROVE_OPTION) { File[] selectedFiles = chooser.getSelectedFiles(); ClassPathEntry[] entries = classPathEntries(selectedFiles, isOutput); // Add the new elements. addElements(entries); } } }); addButton(tip(addButton, inputAndOutput ? isOutput ? "addOutputTip" : "addInputTip" : "addTip")); } protected void addEditButton() { JButton editButton = new JButton(msg("edit")); editButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { boolean isOutput = false; int[] selectedIndices = list.getSelectedIndices(); // Copy the Object array into a File array. File[] selectedFiles = new File[selectedIndices.length]; for (int index = 0; index < selectedFiles.length; index++) { ClassPathEntry entry = (ClassPathEntry)listModel.getElementAt(selectedIndices[index]); isOutput = entry.isOutput(); selectedFiles[index] = entry.getFile(); } chooser.setDialogTitle(msg("chooseJars")); // Up to JDK 1.3.1, setSelectedFiles doesn't show in the file // chooser, so we just use setSelectedFile first. It also sets // the current directory. chooser.setSelectedFile(selectedFiles[0].getAbsoluteFile()); chooser.setSelectedFiles(selectedFiles); int returnValue = chooser.showOpenDialog(owner); if (returnValue == JFileChooser.APPROVE_OPTION) { selectedFiles = chooser.getSelectedFiles(); ClassPathEntry[] entries = classPathEntries(selectedFiles, isOutput); // If there are the same number of files selected now as // there were before, we can just replace the old ones. if (selectedIndices.length == selectedFiles.length) { // Replace the old elements. setElementsAt(entries, selectedIndices); } else { // Remove the old elements. removeElementsAt(selectedIndices); // Add the new elements. addElements(entries); } } } }); addButton(tip(editButton, "editTip")); } protected void addFilterButton() { JButton filterButton = new JButton(msg("filter")); filterButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!list.isSelectionEmpty()) { int[] selectedIndices = list.getSelectedIndices(); // Put the filters of the first selected entry in the dialog. getFiltersFrom(selectedIndices[0]); int returnValue = filterDialog.showDialog(); if (returnValue == FilterDialog.APPROVE_OPTION) { // Apply the entered filters to all selected entries. setFiltersAt(selectedIndices); } } } }); addButton(tip(filterButton, "filterTip")); } /** * Sets the ClassPath to be represented in this panel. */ public void setClassPath(ClassPath classPath) { listModel.clear(); if (classPath != null) { for (int index = 0; index < classPath.size(); index++) { listModel.addElement(classPath.get(index)); } } // Make sure the selection buttons are properly enabled, // since the clear method doesn't seem to notify the listener. enableSelectionButtons(); } /** * Returns the ClassPath currently represented in this panel. */ public ClassPath getClassPath() { int size = listModel.size(); if (size == 0) { return null; } ClassPath classPath = new ClassPath(); for (int index = 0; index < size; index++) { classPath.add((ClassPathEntry)listModel.get(index)); } return classPath; } /** * Converts the given array of File objects into a corresponding array of * ClassPathEntry objects. */ private ClassPathEntry[] classPathEntries(File[] files, boolean isOutput) { ClassPathEntry[] entries = new ClassPathEntry[files.length]; for (int index = 0; index < entries.length; index++) { entries[index] = new ClassPathEntry(files[index], isOutput); } return entries; } /** * Sets up the filter dialog with the filters from the specified class path * entry. */ private void getFiltersFrom(int index) { ClassPathEntry firstEntry = (ClassPathEntry)listModel.get(index); filterDialog.setFilter(firstEntry.getFilter()); filterDialog.setApkFilter(firstEntry.getApkFilter()); filterDialog.setJarFilter(firstEntry.getJarFilter()); filterDialog.setAarFilter(firstEntry.getAarFilter()); filterDialog.setWarFilter(firstEntry.getWarFilter()); filterDialog.setEarFilter(firstEntry.getEarFilter()); filterDialog.setJmodFilter(firstEntry.getJmodFilter()); filterDialog.setZipFilter(firstEntry.getZipFilter()); } /** * Applies the entered filter to the specified class path entries. * Any previously set filters are discarded. */ private void setFiltersAt(int[] indices) { for (int index = indices.length - 1; index >= 0; index--) { ClassPathEntry entry = (ClassPathEntry)listModel.get(indices[index]); entry.setFilter(filterDialog.getFilter()); entry.setApkFilter(filterDialog.getApkFilter()); entry.setJarFilter(filterDialog.getJarFilter()); entry.setAarFilter(filterDialog.getAarFilter()); entry.setWarFilter(filterDialog.getWarFilter()); entry.setEarFilter(filterDialog.getEarFilter()); entry.setJmodFilter(filterDialog.getJmodFilter()); entry.setZipFilter(filterDialog.getZipFilter()); } // Make sure they are selected and thus repainted. list.setSelectedIndices(indices); } /** * Attaches the tool tip from the GUI resources that corresponds to the * given key, to the given component. */ private static JComponent tip(JComponent component, String messageKey) { component.setToolTipText(msg(messageKey)); return component; } /** * Returns the message from the GUI resources that corresponds to the given * key. */ private static String msg(String messageKey) { return GUIResources.getMessage(messageKey); } /** * This ListCellRenderer renders ClassPathEntry objects. */ private class MyListCellRenderer implements ListCellRenderer { private static final String ARROW_IMAGE_FILE = "arrow.gif"; private final JPanel cellPanel = new JPanel(new GridBagLayout()); private final JLabel iconLabel = new JLabel("", JLabel.RIGHT); private final JLabel jarNameLabel = new JLabel("", JLabel.RIGHT); private final JLabel filterLabel = new JLabel("", JLabel.RIGHT); private final Icon arrowIcon; public MyListCellRenderer() { GridBagConstraints jarNameLabelConstraints = new GridBagConstraints(); jarNameLabelConstraints.anchor = GridBagConstraints.WEST; jarNameLabelConstraints.insets = new Insets(1, 2, 1, 2); GridBagConstraints filterLabelConstraints = new GridBagConstraints(); filterLabelConstraints.gridwidth = GridBagConstraints.REMAINDER; filterLabelConstraints.fill = GridBagConstraints.HORIZONTAL; filterLabelConstraints.weightx = 1.0; filterLabelConstraints.anchor = GridBagConstraints.EAST; filterLabelConstraints.insets = jarNameLabelConstraints.insets; arrowIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(this.getClass().getResource(ARROW_IMAGE_FILE))); cellPanel.add(iconLabel, jarNameLabelConstraints); cellPanel.add(jarNameLabel, jarNameLabelConstraints); cellPanel.add(filterLabel, filterLabelConstraints); } // Implementations for ListCellRenderer. public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { ClassPathEntry entry = (ClassPathEntry)value; // Prepend an arrow to the output entries. if (inputAndOutput && entry.isOutput()) { iconLabel.setIcon(arrowIcon); } else { iconLabel.setIcon(null); } // Set the entry name text. jarNameLabel.setText(entry.getName()); // Set the filter text. StringBuffer filter = null; filter = appendFilter(filter, entry.getZipFilter()); filter = appendFilter(filter, entry.getJmodFilter()); filter = appendFilter(filter, entry.getEarFilter()); filter = appendFilter(filter, entry.getWarFilter()); filter = appendFilter(filter, entry.getAarFilter()); filter = appendFilter(filter, entry.getJarFilter()); filter = appendFilter(filter, entry.getApkFilter()); filter = appendFilter(filter, entry.getFilter()); if (filter != null) { filter.append(')'); } filterLabel.setText(filter != null ? filter.toString() : ""); // Set the colors. if (isSelected) { cellPanel.setBackground(list.getSelectionBackground()); jarNameLabel.setForeground(list.getSelectionForeground()); filterLabel.setForeground(list.getSelectionForeground()); } else { cellPanel.setBackground(list.getBackground()); jarNameLabel.setForeground(list.getForeground()); filterLabel.setForeground(list.getForeground()); } // Make the font color red if this is an input file that can't be read. if (!(inputAndOutput && entry.isOutput()) && !entry.getFile().canRead()) { jarNameLabel.setForeground(Color.red); } cellPanel.setOpaque(true); return cellPanel; } private StringBuffer appendFilter(StringBuffer filter, List additionalFilter) { if (filter != null) { filter.append(';'); } if (additionalFilter != null) { if (filter == null) { filter = new StringBuffer().append('('); } filter.append(ListUtil.commaSeparatedString(additionalFilter, true)); } return filter; } } } ================================================ FILE: gui/src/proguard/gui/ClassSpecificationDialog.java ================================================ /* * 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.gui; import proguard.*; import proguard.classfile.AccessConstants; import proguard.classfile.util.ClassUtil; import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*; import java.util.List; /** * This JDialog enables the user to specify a class specification. * * @author Eric Lafortune */ final class ClassSpecificationDialog extends JDialog { /** * Return value if the dialog is canceled (with the Cancel button or by * closing the dialog window). */ public static final int CANCEL_OPTION = 1; /** * Return value if the dialog is approved (with the Ok button). */ public static final int APPROVE_OPTION = 0; private final JTextArea commentsTextArea = new JTextArea(4, 20); private final JCheckBox keepClassesCheckBox = new JCheckBox(msg("keepClasses")); private final JCheckBox keepClassMembersCheckBox = new JCheckBox(msg("keepClassMembers")); private final JCheckBox keepClassesWithMembersCheckBox = new JCheckBox(msg("keepClassesWithMembers")); private final JCheckBox keepDescriptorClassesCheckBox = new JCheckBox(msg("keepDescriptorClasses")); private final JCheckBox keepCodeCheckBox = new JCheckBox(msg("keepCode")); private final JCheckBox allowShrinkingCheckBox = new JCheckBox(msg("allowShrinking")); private final JCheckBox allowOptimizationCheckBox = new JCheckBox(msg("allowOptimization")); private final JCheckBox allowObfuscationCheckBox = new JCheckBox(msg("allowObfuscation")); private final JTextField conditionCommentsField = new JTextField(20); private final ClassSpecificationDialog conditionDialog; private final JRadioButton[] publicRadioButtons; private final JRadioButton[] finalRadioButtons; private final JRadioButton[] abstractRadioButtons; private final JRadioButton[] interfaceRadioButtons; private final JRadioButton[] annotationRadioButtons; private final JRadioButton[] enumRadioButtons; private final JRadioButton[] syntheticRadioButtons; private final JTextField annotationTypeTextField = new JTextField(20); private final JTextField classNameTextField = new JTextField(20); private final JTextField extendsAnnotationTypeTextField = new JTextField(20); private final JTextField extendsClassNameTextField = new JTextField(20); private final MemberSpecificationsPanel memberSpecificationsPanel; private int returnValue; public ClassSpecificationDialog(final JFrame owner, boolean includeKeepSettings, boolean includeFieldButton) { super(owner, msg("specifyClasses"), true); setResizable(true); // Create some constraints that can be reused. GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.insets = new Insets(1, 2, 1, 2); GridBagConstraints constraintsStretch = new GridBagConstraints(); constraintsStretch.fill = GridBagConstraints.HORIZONTAL; constraintsStretch.weightx = 1.0; constraintsStretch.anchor = GridBagConstraints.WEST; constraintsStretch.insets = constraints.insets; GridBagConstraints constraintsLast = new GridBagConstraints(); constraintsLast.gridwidth = GridBagConstraints.REMAINDER; constraintsLast.anchor = GridBagConstraints.WEST; constraintsLast.insets = constraints.insets; GridBagConstraints constraintsLastStretch = new GridBagConstraints(); constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER; constraintsLastStretch.fill = GridBagConstraints.HORIZONTAL; constraintsLastStretch.weightx = 1.0; constraintsLastStretch.anchor = GridBagConstraints.WEST; constraintsLastStretch.insets = constraints.insets; GridBagConstraints panelConstraints = new GridBagConstraints(); panelConstraints.gridwidth = GridBagConstraints.REMAINDER; panelConstraints.fill = GridBagConstraints.HORIZONTAL; panelConstraints.weightx = 1.0; panelConstraints.weighty = 0.0; panelConstraints.anchor = GridBagConstraints.NORTHWEST; panelConstraints.insets = constraints.insets; GridBagConstraints stretchPanelConstraints = new GridBagConstraints(); stretchPanelConstraints.gridwidth = GridBagConstraints.REMAINDER; stretchPanelConstraints.fill = GridBagConstraints.BOTH; stretchPanelConstraints.weightx = 1.0; stretchPanelConstraints.weighty = 1.0; stretchPanelConstraints.anchor = GridBagConstraints.NORTHWEST; stretchPanelConstraints.insets = constraints.insets; GridBagConstraints labelConstraints = new GridBagConstraints(); labelConstraints.anchor = GridBagConstraints.CENTER; labelConstraints.insets = new Insets(2, 10, 2, 10); GridBagConstraints lastLabelConstraints = new GridBagConstraints(); lastLabelConstraints.gridwidth = GridBagConstraints.REMAINDER; lastLabelConstraints.anchor = GridBagConstraints.CENTER; lastLabelConstraints.insets = labelConstraints.insets; GridBagConstraints advancedButtonConstraints = new GridBagConstraints(); advancedButtonConstraints.weightx = 1.0; advancedButtonConstraints.weighty = 1.0; advancedButtonConstraints.anchor = GridBagConstraints.SOUTHWEST; advancedButtonConstraints.insets = new Insets(4, 4, 8, 4); GridBagConstraints okButtonConstraints = new GridBagConstraints(); okButtonConstraints.weightx = 1.0; okButtonConstraints.weighty = 1.0; okButtonConstraints.anchor = GridBagConstraints.SOUTHEAST; okButtonConstraints.insets = advancedButtonConstraints.insets; GridBagConstraints cancelButtonConstraints = new GridBagConstraints(); cancelButtonConstraints.gridwidth = GridBagConstraints.REMAINDER; cancelButtonConstraints.weighty = 1.0; cancelButtonConstraints.anchor = GridBagConstraints.SOUTHEAST; cancelButtonConstraints.insets = advancedButtonConstraints.insets; GridBagLayout layout = new GridBagLayout(); Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED); // Create the comments panel. JPanel commentsPanel = new JPanel(layout); commentsPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("comments"))); JScrollPane commentsScrollPane = new JScrollPane(commentsTextArea); commentsScrollPane.setBorder(classNameTextField.getBorder()); commentsPanel.add(tip(commentsScrollPane, "commentsTip"), constraintsLastStretch); // Create the keep option panel. JPanel keepOptionPanel = new JPanel(layout); keepOptionPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("keepTitle"))); keepOptionPanel.add(tip(keepClassesCheckBox, "keepClassesTip"), constraintsLastStretch); keepOptionPanel.add(tip(keepClassMembersCheckBox, "keepClassMembersTip"), constraintsLastStretch); keepOptionPanel.add(tip(keepClassesWithMembersCheckBox, "keepClassesWithMembersTip"), constraintsLastStretch); // Create the also keep panel. final JPanel alsoKeepOptionPanel = new JPanel(layout); alsoKeepOptionPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("alsoKeepTitle"))); alsoKeepOptionPanel.add(tip(keepDescriptorClassesCheckBox, "keepDescriptorClassesTip"), constraintsLastStretch); alsoKeepOptionPanel.add(tip(keepCodeCheckBox, "keepCodeTip"), constraintsLastStretch); // Create the allow option panel. final JPanel allowOptionPanel = new JPanel(layout); allowOptionPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("allowTitle"))); allowOptionPanel.add(tip(allowShrinkingCheckBox, "allowShrinkingTip"), constraintsLastStretch); allowOptionPanel.add(tip(allowOptimizationCheckBox, "allowOptimizationTip"), constraintsLastStretch); allowOptionPanel.add(tip(allowObfuscationCheckBox, "allowObfuscationTip"), constraintsLastStretch); conditionDialog = includeKeepSettings ? new ClassSpecificationDialog(owner, false, true) : null; // Create the condition panel. final JPanel conditionPanel = new JPanel(layout); conditionPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("conditionTitle"))); final JButton conditionButton = new JButton(msg("edit")); conditionButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { final ClassSpecification originalCondition = conditionDialog.getClassSpecification(); int returnValue = conditionDialog.showDialog(); if (returnValue == APPROVE_OPTION) { // Update the condition label. ClassSpecification condition = conditionDialog.getClassSpecification(); conditionCommentsField.setText(label(condition.equals(new ClassSpecification()) ? null : condition)); } else { // Reset to the original condition. conditionDialog.setClassSpecification(originalCondition); } } }); // The comments can only be edited in the dialog. conditionCommentsField.setEditable(false); conditionPanel.add(tip(conditionCommentsField, "commentsTip"), constraintsStretch); conditionPanel.add(tip(conditionButton, "editConditionTip"), constraintsLast); // Create the access panel. JPanel accessPanel = new JPanel(layout); accessPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("access"))); accessPanel.add(Box.createGlue(), labelConstraints); accessPanel.add(tip(new JLabel(msg("required")), "requiredTip"), labelConstraints); accessPanel.add(tip(new JLabel(msg("not")), "notTip"), labelConstraints); accessPanel.add(tip(new JLabel(msg("dontCare")), "dontCareTip"), labelConstraints); accessPanel.add(Box.createGlue(), constraintsLastStretch); publicRadioButtons = addRadioButtonTriplet("Public", accessPanel); finalRadioButtons = addRadioButtonTriplet("Final", accessPanel); abstractRadioButtons = addRadioButtonTriplet("Abstract", accessPanel); interfaceRadioButtons = addRadioButtonTriplet("Interface", accessPanel); annotationRadioButtons = addRadioButtonTriplet("Annotation", accessPanel); enumRadioButtons = addRadioButtonTriplet("Enum", accessPanel); syntheticRadioButtons = addRadioButtonTriplet("Synthetic", accessPanel); // Create the annotation type panel. final JPanel annotationTypePanel = new JPanel(layout); annotationTypePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("annotation"))); annotationTypePanel.add(tip(annotationTypeTextField, "classNameTip"), constraintsLastStretch); // Create the class name panel. JPanel classNamePanel = new JPanel(layout); classNamePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("class"))); classNamePanel.add(tip(classNameTextField, "classNameTip"), constraintsLastStretch); // Create the extends annotation type panel. final JPanel extendsAnnotationTypePanel = new JPanel(layout); extendsAnnotationTypePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("extendsImplementsAnnotation"))); extendsAnnotationTypePanel.add(tip(extendsAnnotationTypeTextField, "classNameTip"), constraintsLastStretch); // Create the extends class name panel. JPanel extendsClassNamePanel = new JPanel(layout); extendsClassNamePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("extendsImplementsClass"))); extendsClassNamePanel.add(tip(extendsClassNameTextField, "classNameTip"), constraintsLastStretch); // Create the class member list panel. memberSpecificationsPanel = new MemberSpecificationsPanel(this, includeFieldButton); memberSpecificationsPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("classMembers"))); // Create the Advanced button. final JButton advancedButton = new JButton(msg("basic")); advancedButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { boolean visible = !alsoKeepOptionPanel.isVisible(); alsoKeepOptionPanel .setVisible(visible); allowOptionPanel .setVisible(visible); annotationTypePanel .setVisible(visible); extendsAnnotationTypePanel.setVisible(visible); conditionPanel .setVisible(visible); advancedButton.setText(msg(visible ? "basic" : "advanced")); pack(); } }); advancedButton.doClick(); // Create the Ok button. JButton okButton = new JButton(msg("ok")); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { returnValue = APPROVE_OPTION; hide(); } }); // Create the Cancel button. JButton cancelButton = new JButton(msg("cancel")); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { hide(); } }); // Add all panels to the main panel. JPanel mainPanel = new JPanel(layout); mainPanel.add(tip(commentsPanel, "commentsTip"), panelConstraints); if (includeKeepSettings) { mainPanel.add(tip(keepOptionPanel, "keepTitleTip"), panelConstraints); mainPanel.add(tip(alsoKeepOptionPanel, "alsoKeepTitleTip"), panelConstraints); mainPanel.add(tip(allowOptionPanel, "allowTitleTip"), panelConstraints); mainPanel.add(tip(conditionPanel, "conditionTip"), panelConstraints); } mainPanel.add(tip(accessPanel, "accessTip"), panelConstraints); mainPanel.add(tip(annotationTypePanel, "annotationTip"), panelConstraints); mainPanel.add(tip(classNamePanel, "classTip"), panelConstraints); mainPanel.add(tip(extendsAnnotationTypePanel, "extendsImplementsAnnotationTip"), panelConstraints); mainPanel.add(tip(extendsClassNamePanel, "extendsImplementsClassTip"), panelConstraints); mainPanel.add(tip(memberSpecificationsPanel, "classMembersTip"), stretchPanelConstraints); mainPanel.add(tip(advancedButton, "advancedTip"), advancedButtonConstraints); mainPanel.add(okButton, okButtonConstraints); mainPanel.add(cancelButton, cancelButtonConstraints); getContentPane().add(new JScrollPane(mainPanel)); } /** * Adds a JLabel and three JRadioButton instances in a ButtonGroup to the * given panel with a GridBagLayout, and returns the buttons in an array. */ private JRadioButton[] addRadioButtonTriplet(String labelText, JPanel panel) { GridBagConstraints labelConstraints = new GridBagConstraints(); labelConstraints.anchor = GridBagConstraints.WEST; labelConstraints.insets = new Insets(2, 10, 2, 10); GridBagConstraints buttonConstraints = new GridBagConstraints(); buttonConstraints.insets = labelConstraints.insets; GridBagConstraints lastGlueConstraints = new GridBagConstraints(); lastGlueConstraints.gridwidth = GridBagConstraints.REMAINDER; lastGlueConstraints.weightx = 1.0; // Create the radio buttons. JRadioButton radioButton0 = new JRadioButton(); JRadioButton radioButton1 = new JRadioButton(); JRadioButton radioButton2 = new JRadioButton(); // Put them in a button group. ButtonGroup buttonGroup = new ButtonGroup(); buttonGroup.add(radioButton0); buttonGroup.add(radioButton1); buttonGroup.add(radioButton2); // Add the label and the buttons to the panel. panel.add(new JLabel(labelText), labelConstraints); panel.add(radioButton0, buttonConstraints); panel.add(radioButton1, buttonConstraints); panel.add(radioButton2, buttonConstraints); panel.add(Box.createGlue(), lastGlueConstraints); return new JRadioButton[] { radioButton0, radioButton1, radioButton2 }; } /** * Sets the KeepClassSpecification to be represented in this dialog. */ public void setKeepSpecification(KeepClassSpecification keepClassSpecification) { boolean markClasses = keepClassSpecification.markClasses; boolean markClassMembers = keepClassSpecification.markClassMembers; boolean markConditionally = keepClassSpecification.markConditionally; boolean markDescriptorClasses = keepClassSpecification.markDescriptorClasses; boolean markCodeAttributes = keepClassSpecification.markCodeAttributes; boolean allowShrinking = keepClassSpecification.allowShrinking; boolean allowOptimization = keepClassSpecification.allowOptimization; boolean allowObfuscation = keepClassSpecification.allowObfuscation; ClassSpecification condition = keepClassSpecification.condition; // Set the other check boxes. keepClassesCheckBox .setSelected(markClasses); keepClassMembersCheckBox .setSelected(markClassMembers); keepClassesWithMembersCheckBox.setSelected(markDescriptorClasses); keepDescriptorClassesCheckBox .setSelected(markDescriptorClasses); keepCodeCheckBox .setSelected(markCodeAttributes); allowShrinkingCheckBox .setSelected(allowShrinking); allowOptimizationCheckBox .setSelected(allowOptimization); allowObfuscationCheckBox .setSelected(allowObfuscation); // Set the condition comment and dialog. conditionCommentsField.setText(label(condition)); conditionDialog.setClassSpecification(condition != null ? condition : new ClassSpecification()); // Set the rest of the class specification. setClassSpecification(keepClassSpecification); } /** * Sets the ClassSpecification to be represented in this dialog. */ public void setClassSpecification(ClassSpecification classSpecification) { String comments = classSpecification.comments; String annotationType = classSpecification.annotationType; String className = classSpecification.className; String extendsAnnotationType = classSpecification.extendsAnnotationType; String extendsClassName = classSpecification.extendsClassName; List keepFieldOptions = classSpecification.fieldSpecifications; List keepMethodOptions = classSpecification.methodSpecifications; // Set the comments text area. commentsTextArea.setText(comments == null ? "" : comments); // Set the access radio buttons. setClassSpecificationRadioButtons(classSpecification, AccessConstants.PUBLIC, publicRadioButtons); setClassSpecificationRadioButtons(classSpecification, AccessConstants.FINAL, finalRadioButtons); setClassSpecificationRadioButtons(classSpecification, AccessConstants.ABSTRACT, abstractRadioButtons); setClassSpecificationRadioButtons(classSpecification, AccessConstants.INTERFACE, interfaceRadioButtons); setClassSpecificationRadioButtons(classSpecification, AccessConstants.ANNOTATION, annotationRadioButtons); setClassSpecificationRadioButtons(classSpecification, AccessConstants.ENUM, enumRadioButtons); setClassSpecificationRadioButtons(classSpecification, AccessConstants.SYNTHETIC, syntheticRadioButtons); // Set the class and annotation text fields. annotationTypeTextField .setText(annotationType == null ? "" : ClassUtil.externalType(annotationType)); classNameTextField .setText(className == null ? "*" : ClassUtil.externalClassName(className)); extendsAnnotationTypeTextField.setText(extendsAnnotationType == null ? "" : ClassUtil.externalType(extendsAnnotationType)); extendsClassNameTextField .setText(extendsClassName == null ? "" : ClassUtil.externalClassName(extendsClassName)); // Set the keep class member option list. memberSpecificationsPanel.setMemberSpecifications(keepFieldOptions, keepMethodOptions); } /** * Returns the KeepClassSpecification currently represented in this dialog. */ public KeepClassSpecification getKeepSpecification() { boolean markClasses = keepClassesCheckBox .isSelected(); boolean markClassMembers = keepClassMembersCheckBox .isSelected(); boolean markConditionally = keepClassesWithMembersCheckBox.isSelected(); boolean markDescriptorClasses = keepDescriptorClassesCheckBox .isSelected(); boolean markCodeAttributes = keepCodeCheckBox .isSelected(); boolean allowShrinking = allowShrinkingCheckBox .isSelected(); boolean allowOptimization = allowOptimizationCheckBox .isSelected(); boolean allowObfuscation = allowObfuscationCheckBox .isSelected(); ClassSpecification condition = conditionDialog .getClassSpecification(); return new KeepClassSpecification(markClasses, markClassMembers, markConditionally, markDescriptorClasses, markCodeAttributes, allowShrinking, allowOptimization, allowObfuscation, condition.equals(new ClassSpecification()) ? null : condition, getClassSpecification()); } /** * Returns the ClassSpecification currently represented in this dialog. */ public ClassSpecification getClassSpecification() { String comments = commentsTextArea.getText(); String annotationType = annotationTypeTextField.getText(); String className = classNameTextField.getText(); String extendsAnnotationType = extendsAnnotationTypeTextField.getText(); String extendsClassName = extendsClassNameTextField.getText(); ClassSpecification classSpecification = new ClassSpecification(comments.equals("") ? null : comments, 0, 0, annotationType.equals("") ? null : ClassUtil.internalType(annotationType), className.equals("") || className.equals("*") ? null : ClassUtil.internalClassName(className), extendsAnnotationType.equals("") ? null : ClassUtil.internalType(extendsAnnotationType), extendsClassName.equals("") ? null : ClassUtil.internalClassName(extendsClassName)); // Also get the access radio button settings. getClassSpecificationRadioButtons(classSpecification, AccessConstants.PUBLIC, publicRadioButtons); getClassSpecificationRadioButtons(classSpecification, AccessConstants.FINAL, finalRadioButtons); getClassSpecificationRadioButtons(classSpecification, AccessConstants.ABSTRACT, abstractRadioButtons); getClassSpecificationRadioButtons(classSpecification, AccessConstants.INTERFACE, interfaceRadioButtons); getClassSpecificationRadioButtons(classSpecification, AccessConstants.ANNOTATION, annotationRadioButtons); getClassSpecificationRadioButtons(classSpecification, AccessConstants.ENUM, enumRadioButtons); getClassSpecificationRadioButtons(classSpecification, AccessConstants.SYNTHETIC, syntheticRadioButtons); // Get the keep class member option lists. classSpecification.fieldSpecifications = memberSpecificationsPanel.getMemberSpecifications(true); classSpecification.methodSpecifications = memberSpecificationsPanel.getMemberSpecifications(false); return classSpecification; } /** * Shows this dialog. This method only returns when the dialog is closed. * * @return CANCEL_OPTION or APPROVE_OPTION, * depending on the choice of the user. */ public int showDialog() { returnValue = CANCEL_OPTION; // Open the dialog in the right place, then wait for it to be closed, // one way or another. pack(); setLocationRelativeTo(getOwner()); show(); return returnValue; } /** * Returns a suitable label summarizing the given class specification. */ public String label(ClassSpecification classSpecification) { return label(classSpecification, -1); } /** * Returns a suitable label summarizing the given class specification at * some given index. */ public String label(ClassSpecification classSpecification, int index) { return classSpecification == null ? msg("none") : classSpecification.comments != null ? classSpecification.comments.trim() : classSpecification.className != null ? (msg("class") + ' ' + ClassUtil.externalClassName(classSpecification.className)) : classSpecification.annotationType != null ? (msg("classesAnnotatedWith") + ' ' + ClassUtil.externalType(classSpecification.annotationType)) : classSpecification.extendsClassName != null ? (msg("extensionsOf") + ' ' + ClassUtil.externalClassName(classSpecification.extendsClassName)) : classSpecification.extendsAnnotationType != null ? (msg("extensionsOfClassesAnnotatedWith") + ' ' + ClassUtil.externalType(classSpecification.extendsAnnotationType)) : index >= 0 ? (msg("specificationNumber") + index) : msg("specification"); } /** * Sets the appropriate radio button of a given triplet, based on the access * flags of the given keep option. */ private void setClassSpecificationRadioButtons(ClassSpecification classSpecification, int flag, JRadioButton[] radioButtons) { int index = (classSpecification.requiredSetAccessFlags & flag) != 0 ? 0 : (classSpecification.requiredUnsetAccessFlags & flag) != 0 ? 1 : 2; radioButtons[index].setSelected(true); } /** * Updates the access flag of the given keep option, based on the given radio * button triplet. */ private void getClassSpecificationRadioButtons(ClassSpecification classSpecification, int flag, JRadioButton[] radioButtons) { if (radioButtons[0].isSelected()) { classSpecification.requiredSetAccessFlags |= flag; } else if (radioButtons[1].isSelected()) { classSpecification.requiredUnsetAccessFlags |= flag; } } /** * Attaches the tool tip from the GUI resources that corresponds to the * given key, to the given component. */ private static JComponent tip(JComponent component, String messageKey) { component.setToolTipText(msg(messageKey)); return component; } /** * Returns the message from the GUI resources that corresponds to the given * key. */ private static String msg(String messageKey) { return GUIResources.getMessage(messageKey); } } ================================================ FILE: gui/src/proguard/gui/ClassSpecificationsPanel.java ================================================ /* * 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.gui; import proguard.ClassSpecification; import proguard.classfile.util.ClassUtil; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.util.List; /** * This ListPanel enables the user to add, edit, move, and remove * ClassSpecification entries in a list. * * @author Eric Lafortune */ class ClassSpecificationsPanel extends ListPanel { protected final ClassSpecificationDialog classSpecificationDialog; public ClassSpecificationsPanel(JFrame owner, boolean includeKeepSettings, boolean includeFieldButton) { super(); list.setCellRenderer(new MyListCellRenderer()); classSpecificationDialog = new ClassSpecificationDialog(owner, includeKeepSettings, includeFieldButton); addAddButton(); addEditButton(); addRemoveButton(); addUpButton(); addDownButton(); enableSelectionButtons(); } protected void addAddButton() { JButton addButton = new JButton(msg("add")); addButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setClassSpecification(createClassSpecification()); int returnValue = classSpecificationDialog.showDialog(); if (returnValue == ClassSpecificationDialog.APPROVE_OPTION) { // Add the new element. addElement(getClassSpecification()); } } }); addButton(tip(addButton, "addTip")); } protected void addEditButton() { JButton editButton = new JButton(msg("edit")); editButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ClassSpecification selectedClassSpecification = (ClassSpecification)list.getSelectedValue(); setClassSpecification(selectedClassSpecification); int returnValue = classSpecificationDialog.showDialog(); if (returnValue == ClassSpecificationDialog.APPROVE_OPTION) { // Replace the old element. setElementAt(getClassSpecification(), list.getSelectedIndex()); } } }); addButton(tip(editButton, "editTip")); } protected ClassSpecification createClassSpecification() { return new ClassSpecification(); } protected void setClassSpecification(ClassSpecification classSpecification) { classSpecificationDialog.setClassSpecification(classSpecification); } protected ClassSpecification getClassSpecification() { return classSpecificationDialog.getClassSpecification(); } /** * Sets the ClassSpecification objects to be represented in this panel. */ public void setClassSpecifications(List classSpecifications) { listModel.clear(); if (classSpecifications != null) { for (int index = 0; index < classSpecifications.size(); index++) { listModel.addElement(classSpecifications.get(index)); } } // Make sure the selection buttons are properly enabled, // since the clear method doesn't seem to notify the listener. enableSelectionButtons(); } /** * Returns the ClassSpecification objects currently represented in this panel. */ public List getClassSpecifications() { int size = listModel.size(); if (size == 0) { return null; } List classSpecifications = new ArrayList(size); for (int index = 0; index < size; index++) { classSpecifications.add(listModel.get(index)); } return classSpecifications; } /** * Attaches the tool tip from the GUI resources that corresponds to the * given key, to the given component. */ private static JComponent tip(JComponent component, String messageKey) { component.setToolTipText(msg(messageKey)); return component; } /** * Returns the message from the GUI resources that corresponds to the given * key. */ private static String msg(String messageKey) { return GUIResources.getMessage(messageKey); } /** * This ListCellRenderer renders ClassSpecification objects. */ private class MyListCellRenderer implements ListCellRenderer { private final JLabel label = new JLabel(); // Implementations for ListCellRenderer. public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { ClassSpecification classSpecification = (ClassSpecification)value; label.setText(classSpecificationDialog.label(classSpecification, index)); if (isSelected) { label.setBackground(list.getSelectionBackground()); label.setForeground(list.getSelectionForeground()); } else { label.setBackground(list.getBackground()); label.setForeground(list.getForeground()); } label.setOpaque(true); return label; } } } ================================================ FILE: gui/src/proguard/gui/ExtensionFileFilter.java ================================================ /* * 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.gui; import javax.swing.filechooser.FileFilter; import java.io.File; /** * This FileFilter accepts files that end in one of the given * extensions. * * @author Eric Lafortune */ final class ExtensionFileFilter extends FileFilter { private final String description; private final String[] extensions; /** * Creates a new ExtensionFileFilter. * @param description a description of the filter. * @param extensions an array of acceptable extensions. */ public ExtensionFileFilter(String description, String[] extensions) { this.description = description; this.extensions = extensions; } // Implemntations for FileFilter public String getDescription() { return description; } public boolean accept(File file) { if (file.isDirectory()) { return true; } String fileName = file.getName().toLowerCase(); for (int index = 0; index < extensions.length; index++) { if (fileName.endsWith(extensions[index])) { return true; } } return false; } } ================================================ FILE: gui/src/proguard/gui/FilterBuilder.java ================================================ /* * 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.gui; import javax.swing.*; /** * This class builds filters corresponding to the selections and names of a * given list of check boxes. */ public class FilterBuilder { private JCheckBox[] checkBoxes; private char separator; /** * Creates a new FilterBuilder. * @param checkBoxes the check boxes with names and selections that should * be reflected in the output filter. * @param separator the separator for the names in the check boxes. */ public FilterBuilder(JCheckBox[] checkBoxes, char separator) { this.checkBoxes = checkBoxes; this.separator = separator; } /** * Builds a filter for the current names and selections of the check boxes. */ public String buildFilter() { StringBuffer positive = new StringBuffer(); StringBuffer negative = new StringBuffer(); buildFilter("", positive, negative); return positive.length() <= negative.length() ? positive.toString() : negative.toString(); } /** * Builds two versions of the filter for the given prefix. * @param prefix the prefix. * @param positive the filter to be extended, assuming the matching * strings are accepted. * @param negative the filter to be extended, assuming the matching * strings are rejected. */ private void buildFilter(String prefix, StringBuffer positive, StringBuffer negative) { int positiveCount = 0; int negativeCount = 0; // Count all selected and unselected check boxes with the prefix. for (int index = 0; index < checkBoxes.length; index++) { JCheckBox checkBox = checkBoxes[index]; String name = checkBox.getText(); if (name.startsWith(prefix)) { if (checkBox.isSelected()) { positiveCount++; } else { negativeCount++; } } } // Are there only unselected check boxes? if (positiveCount == 0) { // Extend the positive filter with exceptions and return. if (positive.length() > 0) { positive.append(','); } positive.append('!').append(prefix); if (prefix.length() == 0 || prefix.charAt(prefix.length()-1) == separator) { positive.append('*'); } return; } // Are there only selected check boxes? if (negativeCount == 0) { // Extend the negative filter with exceptions and return. if (negative.length() > 0) { negative.append(','); } negative.append(prefix); if (prefix.length() == 0 || prefix.charAt(prefix.length()-1) == separator) { negative.append('*'); } return; } // Create new positive and negative filters for names starting with the // prefix only. StringBuffer positiveFilter = new StringBuffer(); StringBuffer negativeFilter = new StringBuffer(); String newPrefix = null; for (int index = 0; index < checkBoxes.length; index++) { String name = checkBoxes[index].getText(); if (name.startsWith(prefix)) { if (newPrefix == null || !name.startsWith(newPrefix)) { int prefixIndex = name.indexOf(separator, prefix.length()+1); newPrefix = prefixIndex >= 0 ? name.substring(0, prefixIndex+1) : name; buildFilter(newPrefix, positiveFilter, negativeFilter); } } } // Extend the positive filter. if (positiveFilter.length() <= negativeFilter.length() + prefix.length() + 3) { if (positive.length() > 0 && positiveFilter.length() > 0) { positive.append(','); } positive.append(positiveFilter); } else { if (positive.length() > 0 && negativeFilter.length() > 0) { positive.append(','); } positive.append(negativeFilter).append(",!").append(prefix).append('*'); } // Extend the negative filter. if (negativeFilter.length() <= positiveFilter.length() + prefix.length() + 4) { if (negative.length() > 0 && negativeFilter.length() > 0) { negative.append(','); } negative.append(negativeFilter); } else { if (negative.length() > 0 && positiveFilter.length() > 0) { negative.append(','); } negative.append(positiveFilter).append(',').append(prefix).append('*'); } } } ================================================ FILE: gui/src/proguard/gui/FilterDialog.java ================================================ /* * 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.gui; import proguard.util.ListUtil; import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*; import java.util.List; /** * This JDialog allows the user to enter a String. * * @author Eric Lafortune */ public class FilterDialog extends JDialog { /** * Return value if the dialog is canceled (with the Cancel button or by * closing the dialog window). */ public static final int CANCEL_OPTION = 1; /** * Return value if the dialog is approved (with the Ok button). */ public static final int APPROVE_OPTION = 0; private static final String DEFAULT_FILTER = "**"; private static final String DEFAULT_APK_FILTER = "**.apk"; private static final String DEFAULT_JAR_FILTER = "**.jar"; private static final String DEFAULT_AAR_FILTER = "**.aar"; private static final String DEFAULT_WAR_FILTER = "**.war"; private static final String DEFAULT_EAR_FILTER = "**.ear"; private static final String DEFAULT_JMOD_FILTER = "**.jmod"; private static final String DEFAULT_ZIP_FILTER = "**.zip"; private final JTextField filterTextField = new JTextField(40); private final JTextField apkFilterTextField = new JTextField(40); private final JTextField jarFilterTextField = new JTextField(40); private final JTextField aarFilterTextField = new JTextField(40); private final JTextField warFilterTextField = new JTextField(40); private final JTextField earFilterTextField = new JTextField(40); private final JTextField jmodFilterTextField = new JTextField(40); private final JTextField zipFilterTextField = new JTextField(40); private int returnValue; public FilterDialog(JFrame owner, String explanation) { super(owner, true); setResizable(true); // Create some constraints that can be reused. GridBagConstraints textConstraints = new GridBagConstraints(); textConstraints.gridwidth = GridBagConstraints.REMAINDER; textConstraints.fill = GridBagConstraints.HORIZONTAL; textConstraints.weightx = 1.0; textConstraints.weighty = 1.0; textConstraints.anchor = GridBagConstraints.NORTHWEST; textConstraints.insets = new Insets(10, 10, 10, 10); GridBagConstraints labelConstraints = new GridBagConstraints(); labelConstraints.anchor = GridBagConstraints.WEST; labelConstraints.insets = new Insets(1, 2, 1, 2); GridBagConstraints textFieldConstraints = new GridBagConstraints(); textFieldConstraints.gridwidth = GridBagConstraints.REMAINDER; textFieldConstraints.fill = GridBagConstraints.HORIZONTAL; textFieldConstraints.weightx = 1.0; textFieldConstraints.anchor = GridBagConstraints.WEST; textFieldConstraints.insets = labelConstraints.insets; GridBagConstraints panelConstraints = new GridBagConstraints(); panelConstraints.gridwidth = GridBagConstraints.REMAINDER; panelConstraints.fill = GridBagConstraints.HORIZONTAL; panelConstraints.weightx = 1.0; panelConstraints.weighty = 0.0; panelConstraints.anchor = GridBagConstraints.NORTHWEST; panelConstraints.insets = labelConstraints.insets; GridBagConstraints okButtonConstraints = new GridBagConstraints(); okButtonConstraints.weightx = 1.0; okButtonConstraints.weighty = 1.0; okButtonConstraints.anchor = GridBagConstraints.SOUTHEAST; okButtonConstraints.insets = new Insets(4, 4, 8, 4); GridBagConstraints cancelButtonConstraints = new GridBagConstraints(); cancelButtonConstraints.gridwidth = GridBagConstraints.REMAINDER; cancelButtonConstraints.weighty = 1.0; cancelButtonConstraints.anchor = GridBagConstraints.SOUTHEAST; cancelButtonConstraints.insets = okButtonConstraints.insets; GridBagLayout layout = new GridBagLayout(); Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED); // Create the panel with the explanation. JTextArea explanationTextArea = new JTextArea(explanation, 3, 0); explanationTextArea.setOpaque(false); explanationTextArea.setEditable(false); explanationTextArea.setLineWrap(true); explanationTextArea.setWrapStyleWord(true); // Create the filter labels. JLabel filterLabel = new JLabel(msg("nameFilter")); JLabel apkFilterLabel = new JLabel(msg("apkNameFilter")); JLabel jarFilterLabel = new JLabel(msg("jarNameFilter")); JLabel aarFilterLabel = new JLabel(msg("aarNameFilter")); JLabel warFilterLabel = new JLabel(msg("warNameFilter")); JLabel earFilterLabel = new JLabel(msg("earNameFilter")); JLabel jmodFilterLabel = new JLabel(msg("jmodNameFilter")); JLabel zipFilterLabel = new JLabel(msg("zipNameFilter")); // Create the filter panel. JPanel filterPanel = new JPanel(layout); filterPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("filters"))); filterPanel.add(explanationTextArea, textConstraints); filterPanel.add(tip(filterLabel, "nameFilterTip"), labelConstraints); filterPanel.add(tip(filterTextField, "fileNameFilterTip"), textFieldConstraints); filterPanel.add(tip(apkFilterLabel, "apkNameFilterTip"), labelConstraints); filterPanel.add(tip(apkFilterTextField, "fileNameFilterTip"), textFieldConstraints); filterPanel.add(tip(jarFilterLabel, "jarNameFilterTip"), labelConstraints); filterPanel.add(tip(jarFilterTextField, "fileNameFilterTip"), textFieldConstraints); filterPanel.add(tip(aarFilterLabel, "aarNameFilterTip"), labelConstraints); filterPanel.add(tip(aarFilterTextField, "fileNameFilterTip"), textFieldConstraints); filterPanel.add(tip(warFilterLabel, "warNameFilterTip"), labelConstraints); filterPanel.add(tip(warFilterTextField, "fileNameFilterTip"), textFieldConstraints); filterPanel.add(tip(earFilterLabel, "earNameFilterTip"), labelConstraints); filterPanel.add(tip(earFilterTextField, "fileNameFilterTip"), textFieldConstraints); filterPanel.add(tip(jmodFilterLabel, "jmodNameFilterTip"), labelConstraints); filterPanel.add(tip(jmodFilterTextField, "fileNameFilterTip"), textFieldConstraints); filterPanel.add(tip(zipFilterLabel, "zipNameFilterTip"), labelConstraints); filterPanel.add(tip(zipFilterTextField, "fileNameFilterTip"), textFieldConstraints); JButton okButton = new JButton(msg("ok")); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { returnValue = APPROVE_OPTION; hide(); } }); JButton cancelButton = new JButton(msg("cancel")); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { hide(); } }); // Add all panels to the main panel. JPanel mainPanel = new JPanel(layout); mainPanel.add(filterPanel, panelConstraints); mainPanel.add(okButton, okButtonConstraints); mainPanel.add(cancelButton, cancelButtonConstraints); getContentPane().add(mainPanel); } /** * Sets the filter to be represented in this dialog. */ public void setFilter(List filter) { filterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter, true) : DEFAULT_FILTER); } /** * Returns the filter currently represented in this dialog. */ public List getFilter() { String filter = filterTextField.getText(); return filter.equals(DEFAULT_FILTER) ? null : ListUtil.commaSeparatedList(filter); } /** * Sets the apk filter to be represented in this dialog. */ public void setApkFilter(List filter) { apkFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter, true) : DEFAULT_APK_FILTER); } /** * Returns the apk filter currently represented in this dialog. */ public List getApkFilter() { String filter = apkFilterTextField.getText(); return filter.equals(DEFAULT_APK_FILTER) ? null : ListUtil.commaSeparatedList(filter); } /** * Sets the jar filter to be represented in this dialog. */ public void setJarFilter(List filter) { jarFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter, true) : DEFAULT_JAR_FILTER); } /** * Returns the jar filter currently represented in this dialog. */ public List getJarFilter() { String filter = jarFilterTextField.getText(); return filter.equals(DEFAULT_JAR_FILTER) ? null : ListUtil.commaSeparatedList(filter); } /** * Sets the aar filter to be represented in this dialog. */ public void setAarFilter(List filter) { aarFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter, true) : DEFAULT_AAR_FILTER); } /** * Returns the aar filter currently represented in this dialog. */ public List getAarFilter() { String filter = aarFilterTextField.getText(); return filter.equals(DEFAULT_AAR_FILTER) ? null : ListUtil.commaSeparatedList(filter); } /** * Sets the war filter to be represented in this dialog. */ public void setWarFilter(List filter) { warFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter, true) : DEFAULT_WAR_FILTER); } /** * Returns the war filter currently represented in this dialog. */ public List getWarFilter() { String filter = warFilterTextField.getText(); return filter.equals(DEFAULT_WAR_FILTER) ? null : ListUtil.commaSeparatedList(filter); } /** * Sets the ear filter to be represented in this dialog. */ public void setEarFilter(List filter) { earFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter, true) : DEFAULT_EAR_FILTER); } /** * Returns the ear filter currently represented in this dialog. */ public List getEarFilter() { String filter = earFilterTextField.getText(); return filter.equals(DEFAULT_EAR_FILTER) ? null : ListUtil.commaSeparatedList(filter); } /** * Sets the jmod filter to be represented in this dialog. */ public void setJmodFilter(List filter) { jmodFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter, true) : DEFAULT_JMOD_FILTER); } /** * Returns the jmod filter currently represented in this dialog. */ public List getJmodFilter() { String filter = jmodFilterTextField.getText(); return filter.equals(DEFAULT_JMOD_FILTER) ? null : ListUtil.commaSeparatedList(filter); } /** * Sets the zip filter to be represented in this dialog. */ public void setZipFilter(List filter) { zipFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter, true) : DEFAULT_ZIP_FILTER); } /** * Returns the zip filter currently represented in this dialog. */ public List getZipFilter() { String filter = zipFilterTextField.getText(); return filter.equals(DEFAULT_ZIP_FILTER) ? null : ListUtil.commaSeparatedList(filter); } /** * Shows this dialog. This method only returns when the dialog is closed. * * @return CANCEL_OPTION or APPROVE_OPTION, * depending on the choice of the user. */ public int showDialog() { returnValue = CANCEL_OPTION; // Open the dialog in the right place, then wait for it to be closed, // one way or another. pack(); setLocationRelativeTo(getOwner()); show(); return returnValue; } /** * Attaches the tool tip from the GUI resources that corresponds to the * given key, to the given component. */ private static JComponent tip(JComponent component, String messageKey) { component.setToolTipText(msg(messageKey)); return component; } /** * Returns the message from the GUI resources that corresponds to the given * key. */ private static String msg(String messageKey) { return GUIResources.getMessage(messageKey); } } ================================================ FILE: gui/src/proguard/gui/GUIResources.java ================================================ /* * 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.gui; import java.text.MessageFormat; import java.util.ResourceBundle; /** * This class provides some utility methods for working with resource bundles. * * @author Eric Lafortune */ class GUIResources { private static final ResourceBundle messages = ResourceBundle.getBundle(GUIResources.class.getName()); private static final MessageFormat formatter = new MessageFormat(""); /** * Returns an internationalized message, based on its key. */ public static String getMessage(String messageKey) { return messages.getString(messageKey); } /** * Returns an internationalized, formatted message, based on its key, with * the given arguments. */ public static String getMessage(String messageKey, Object[] messageArguments) { formatter.applyPattern(messages.getString(messageKey)); return formatter.format(messageArguments); } } ================================================ FILE: gui/src/proguard/gui/GUIResources.properties ================================================ # ProGuard -- shrinking, optimization, and obfuscation of Java class files. # Copyright (c) 2002-2020 Guardsquare NV # # Tab names. # proGuardTab = ProGuard inputOutputTab = Input/Output shrinkingTab = Shrinking obfuscationTab = Obfuscation optimizationTab = Optimization informationTab = Information processTab = Process reTraceTab = ReTrace # # Splash text. # developed = Developed by Guardsquare shrinking = Shrinking optimization = Optimization obfuscation = Obfuscation preverification = Preverification # # Panel titles. # welcome = Welcome to ProGuard options = Options keepAdditional = Keep additional classes and class members keepNamesAdditional = Keep additional class names and class member names assumeNoSideEffectsAdditional = Assume no side effects for additional methods whyAreYouKeeping = Why are you keeping preverificationAndTargeting = Preverification and targeting consistencyAndCorrectness = Consistency and correctness processingConsole = Processing console reTraceSettings = ReTrace settings deobfuscatedStackTrace = De-obfuscated stack trace keepAdditionalTip = \ If required, keep additional classes, fields, and methods as entry points. keepNamesAdditionalTip = \ If required, keep the names of additional classes, fields, and methods. assumeNoSideEffectsAdditionalTip = \ Optionally specify additional methods that don't have any side effects.
\ Only add entries if you know what you're doing! whyAreYouKeepingTip = \ Ask ProGuard why it is keeping certain classes, fields, or methods. # # Info texts. # proGuardInfo = \ ProGuard is a free class file shrinker, optimizer, obfuscator, and preverifier.\

\ With this GUI, you can create, load, modify, and save ProGuard configurations.\
\ You can then process your code right away, or you can run ProGuard from the \ command line using your saved configuration.\

\ With the ReTrace part of this GUI you can de-obfuscate your stack traces.\

\ ProGuard and ReTrace are written and maintained by Guardsquare.\

\ The ProGuard project is hosted on GitHub: \ https://github.com/Guardsquare/proguard\
\ For usage questions head over to the Guardsquare Community: \ https://community.guardsquare.com \
\ DexGuard and professional support by Guardsquare: \ http://www.guardsquare.com/\

\ Distributed under the GNU General Public License.\
\ Copyright © 2002-2022 Guardsquare NV. processingInfo = \ You can now start processing your code, \ or you can run ProGuard from the command line using your saved configuration. reTraceInfo = \ If you had ProGuard write out a mapping file, \ you can de-obfuscate your obfuscated stack traces with ReTrace!\ \n\n\ You can load an obfuscated stack trace from a file, \ or you can paste it straight into the text area above. # # Titles and labels corresponding to common ProGuard options. # programJars = Program jars, aars, wars, ears, jmods, zips, apks, and directories libraryJars = Library jars, aars, wars, ears, jmods, zips, apks, and directories shrink = Shrink printUsage = Print usage optimize = Optimize allowAccessModification = Allow access modification mergeInterfacesAggressively = Merge interfaces aggressively optimizations = Optimizations optimizationPasses = Optimization passes obfuscate = Obfuscate printMapping = Print mapping applyMapping = Apply mapping obfuscationDictionary = Obfuscation dictionary classObfuscationDictionary = Class obfuscation dictionary packageObfuscationDictionary = Package obfuscation dictionary overloadAggressively = Overload aggressively useUniqueClassMemberNames = Use unique class member names keepPackageNames = Keep package names flattenPackageHierarchy = Flatten package hierarchy repackageClasses = Repackage classes useMixedCaseClassNames = Use mixed-case class names keepAttributes = Keep attributes keepParameterNames = Keep parameter names renameSourceFileAttribute = Rename SourceFile attribute adaptClassStrings = Adapt class strings adaptResourceFileNames = Adapt resource file names adaptResourceFileContents = Adapt resource file contents preverify = Preverify microEdition = Micro Edition android = Android verbose = Verbose note = Note potential mistakes in the configuration warn = Warn about possibly erroneous input ignoreWarnings = Ignore warnings about possibly erroneous input skipNonPublicLibraryClasses = Skip non-public library classes skipNonPublicLibraryClassMembers = Skip non-public library class members keepDirectories = Keep directories forceProcessing = Force processing target = Target targets = 1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,10 printSeeds = Print seeds printConfiguration = Print configuration dump = Print class files mappingFile = Mapping file obfuscatedStackTrace = Obfuscated stack trace programJarsTip = \ The input jars (aars, wars, ears, jmods, zips, apks, directories), followed by
\ their corresponding output jars (wars, ears, jmods, zips, apks, directories). libraryJarsTip = \ The library jars (aars, wars, ears, jmods, zips, directories), on which the program jars depend.
\ The library jars are required for processing, but they are not copied to the output. shrinkTip = \ Remove unused classes, fields, and methods from the output. printUsageTip = \ Print out the list of unused classes, fields, and methods. optimizeTip = \ Optimize the bytecode of the processed classes. allowAccessModificationTip = \ Allow the optimization step to modify the access modifiers of classes, fields, and methods. mergeInterfacesAggressivelyTip = \ Allow interfaces to be merged, even if their implementations don't implement all
\ interface methods. This is not allowed in the Java language, but it is allowed in bytecode. optimizationsTip = \ Specify the types of optimizations to be performed. optimizationsFilterTip = \ A filter for the names of the optimizations to be performed. optimizationsSelectTip = \ Select from the currently available optimizations... optimizationPassesTip = \ Specify the number of optimization passes to be performed. obfuscateTip = \ Obfuscate the names of the processed classes, fields, and methods. printMappingTip = \ Print out the obfuscation mapping of original names to obfuscated names. applyMappingTip = \ Apply the given mapping of original names to obfuscated names. obfuscationDictionaryTip = \ Use the words in the given file for obfuscating field names and method names. classObfuscationDictionaryTip = \ Use the words in the given file for obfuscating class names. packageObfuscationDictionaryTip = \ Use the words in the given file for obfuscating package names. overloadAggressivelyTip = \ Allow fields and methods to get the same obfuscated names, even if only their types or
\ return types differ. This is not allowed in the Java language, but it is allowed in bytecode. useUniqueClassMemberNamesTip = \ Make sure fields and methods get the same obfuscation mapping across classes, even
\ if they are unrelated. This is advisable if the output is to be obfuscated incrementally. keepPackageNamesTip = \ Keep the specified package names from being obfuscated. packageNamesTip = \ An optional comma-separated list of package names,
\ e.g. myapplication,mylibrary.**
\ Possible wildcards:\

    \
  • ? for any single character, except the package separator.\
  • * for any number of any characters, except the package separator.\
  • ** for any number of any characters.\
\ The negator ! is also supported. flattenPackageHierarchyTip = \ Move all packages that are renamed into the given parent package. repackageClassesTip = \ Move all classes that are renamed into the given package. packageTip = \ The optional package name. useMixedCaseClassNamesTip = \ Generate mixed-case obfucated class names. This will complicate unpacking
\ the resulting jars on case-insensitive file systems, should that be necessary. keepAttributesTip = \ Keep the specified optional class file attributes. attributesTip = \ An optional comma-separated list of class file attributes.\
    \
  • "Exceptions,Innerclasses, Signature" are necessary if the output is to be used as a library.\
  • "Deprecated" is optional if the output is to be used as a library.\
  • "LocalVariable*Table" can be useful for debugging.\
  • "Sourcefile,LineNumberTable" are necessary for generating stack traces.\
  • "*Annotations*" is necessary for preserving annotations.\
\ The wildcard * and the negator ! are allowed. keepParameterNamesTip = \ Keep parameter names and types in "LocalVariable*Table" attributes
\ in methods that are not obfuscated. renameSourceFileAttributeTip = \ Put the given string in the "SourceFile" attribute of the processed class files.
\ It will appear as the file name of the classes in stack traces. sourceFileAttributeTip = \ The replacement "SourceFile" string. adaptClassStringsTip = \ Adapt string constants in the specified classes, based
\ on the obfuscated names of corresponding classes. adaptResourceFileNamesTip = \ Rename the specified resource files, based on the
\ obfuscated names of the corresponding class files. adaptResourceFileContentsTip = \ Adapt the contents of the specified resource files, based
\ on the obfuscated names of the processed classes. fileNameFilterTip = \ A filter on file names,
\ e.g. mydirectory1/**,mydirectory2/**
\ Possible wildcards:\
    \
  • ? for any single character, except the directory separator.\
  • * for any number of any characters, except the directory separator.\
  • ** for any number of any characters.\
\ The negator ! is also supported. preverifyTip = \ Preverify the processed classes, for Java Micro Edition or for Java 6. microEditionTip = \ Target Java Micro Edition. androidTip = \ Target Android. verboseTip = \ Print out verbose messages while processing. noteTip = \ Print out notes about special or unusual input. noteFilterTip = \ A filter matching classes for which no notes should be printed. warnTip = \ Print out warnings about possibly erroneous input.
\ Only unset this option if you know what you're doing! warnFilterTip = \ A filter matching classes for which no warnings should be printed. ignoreWarningsTip = \ Ignore any warnings about possibly erroneous input.
\ Only set this option if you know what you're doing! skipNonPublicLibraryClassesTip = \ Skip reading non-public library classes, for efficiency.
\ You may have to unset this option if ProGuard complains about missing classes. skipNonPublicLibraryClassMembersTip = \ Skip reading non-public library fields and methods, for efficiency.
\ You may have to unset this option if ProGuard complains about missing class members. keepDirectoriesTip = \ Keep the specified directories in the output jars, wars, ears, jmods, zips, apks, or directories. directoriesTip = \ A filter on directory names,
\ e.g. mydirectory1,mydirectory2/**
\ Possible wildcards:\
    \
  • ? for any single character, except the directory separator.\
  • * for any number of any characters, except the directory separator.\
  • ** for any number of any characters.\
\ The negator ! is also supported. forceProcessingTip = \ Always process the input, even if the output seems up to date. targetTip = \ Target the specified version of Java. printSeedsTip = \ Print out the list of kept classes, fields, and methods. printConfigurationTip = \ Print out the configuration. dumpTip = \ Print out the internal structure of the processed class files. mappingFileTip = \ The file containing the mapping of original names to obfuscated names. obfuscatedStackTraceTip = \ A stack trace produced by previously obfuscated code. # # Titles and labels corresponding to ProGuard keep options. # keepTitle = Keep keepClasses = Keep classes keepClassMembers = Keep class members keepClassesWithMembers = Keep classes and class members, if members are present alsoKeepTitle = Also keep keepDescriptorClasses = Keep descriptor classes keepCode = Keep code allowTitle = Allow allowShrinking = Allow shrinking allowOptimization = Allow optimization allowObfuscation = Allow obfuscation conditionTitle = Condition keepTitleTip = Keep the specified classes and/or their fields and methods. keepClassesTip = \ Keep the specified classes, fields, and methods as entry points.
\ This is the most common option. keepClassMembersTip = \ Keep the specified fields and methods as entry points. keepClassesWithMembersTip = \ Keep the specified classes, fields, and methods,
\ on the condition that the fields and methods are present. alsoKeepTitleTip = \ Optionally keeping more classes.
\ These are advanced options. keepDescriptorClassesTip = \ Automatically keep the classes in the descriptors of matching
\ fields and methods. Mostly useful for keeping their names. keepCodeTip = \ Don't change the byte code of matching methods. allowTitleTip = \ Optionally relax keeping the specified classes, fields, and methods.
\ These are advanced options. allowShrinkingTip = \ Remove the specified classes, fields, and methods anyway, if they are not used. allowOptimizationTip = \ Optimize the specified classes, fields, and methods as entry points anyway.
\ Only set this option if you know what you're doing! allowObfuscationTip = \ Obfuscate the names of the specified classes, fields, and methods anyway.
\ Only set this option if you know what you're doing! conditionTitleTip = \ Only apply the option if the specified classes, fields, and methods
\ are present. editConditionTip = Edit the condition. # # Further keep titles and labels. # specifyClasses = Specify classes and class members... specifyFields = Specify fields... specifyMethods = Specify methods... comments = Comments access = Access required = Required not = Not dontCare = Don't care annotation = Annotation class = Class extendsImplementsAnnotation = Extends/implements class with annotation extendsImplementsClass = Extends/implements class classMembers = Class members none = (none) classesAnnotatedWith = Classes annotated with extensionsOf = Extensions of extensionsOfClassesAnnotatedWith = Extensions of classes annotated with specification = Specification specificationNumber = Specification # fieldType = Field type returnType = Return type name = Name argumentTypes = Argument types commentsTip = \ Optionally add a comment for this option in the configuration file. accessTip = \ Optionally place constraints on the access modifiers of this element.
\ E.g. only match public elements. requiredTip = \ The access modifier has to be set. notTip = \ The access modifier must not be set. dontCareTip = \ The access modifier is irrelevant. annotationTip = \ Optionally require the given annotation to be present on this element.
\ E.g. only match elements that have an annotation myPackage.MyAnnotation.
\ This is an advanced option. classTip = \ The name of the class or interface. extendsImplementsAnnotationTip = \ Optionally require the given annotation to be present on the
\ extended or implemented class or interface.
\ E.g. only match classes that extend a class that has an annotation
\ myPackage.MyAnnotation.
\ This is an advanced option. extendsImplementsClassTip = \ Optionally require the class to implement or extend the given class or interface.
\ E.g. only match classes that implement an interface myPackage.MyInterface. classMembersTip = \ Optionally keep fields and methods as entry points in the matching class or classes.
\ E.g. keep all public 'get*' methods as entry points. conditionTip = \ Optionally add a condition to the specified option. fieldTypeTip = The field type. returnTypeTip = The method return type, if any. nameTip = The name. argumentTypesTip = The method argument types, if any. classNameTip = \ The class name, e.g. myPackage.MyClass
\ Possible wildcards:\
    \
  • ? for any single character, except the package separator.\
  • * for any number of any characters, except the package separator.\
  • ** for any number of any characters.\
classNamesTip = \ A regular expression to further constrain the class names,
\ e.g. myPackage1.MyClass,myPackage2.**
\ Possible wildcards:\
    \
  • ? for any single character, except the package separator.\
  • * for any number of any characters, except the package separator.\
  • ** for any number of any characters.\
\ The negator ! is also supported. typeTip = \ The type, e.g. int, or java.lang.String[]
\ Possible wildcards:\
    \
  • % for any primitive type.\
  • ? for any single character, except the package separator.\
  • * for any number of any characters, except the package separator.\
  • ** for any number of any characters.\
  • *** (or empty) for any type.\
fieldNameTip = \ The field name, e.g. myField
\ Possible wildcards:\
    \
  • ? for any single character.\
  • * for any number of any characters.\
methodNameTip = \ The method name, e.g. myMethod
\ Possible wildcards:\
    \
  • ? for any single character.\
  • * for any number of any characters.\
argumentTypes2Tip = \ The comma-separated list of argument types,
\ e.g. java.lang.String[],int,boolean
\ Possible wildcards:\
    \
  • % for any primitive type.\
  • ? for any single character, except the package separator.\
  • * for any number of any characters, except the package separator.\
  • ** for any number of any characters.\
  • *** for any type.\
  • ... for any number of any arguments.\
# # Titles and labels corresponding to optimization options. # selectOptimizations = Select optimizations... library = Library field = Field method = Method code = Code library_gsonTip = \ Optimize GSON serialization code. class_marking_finalTip = \ Mark classes as final, whenever possible. class_unboxing_enumTip = \ Simplify enum types to integer constants, whenever possible. class_merging_verticalTip = \ Merge classes vertically in the class hierarchy, whenever possible. class_merging_horizontalTip = \ Merge classes horizontally in the class hierarchy, whenever possible. field_removal_writeonlyTip = \ Remove write-only fields. field_marking_privateTip = \ Mark fields as private, whenever possible. field_generalization_classTip = \ Generalizes the classes of field accesses, whenever possible. field_specialization_typeTip = \ Specializes the types of fields, whenever possible. field_propagation_valueTip = \ Propagate the values of fields across methods. method_marking_privateTip = \ Mark methods as private, whenever possible (devirtualization). method_marking_staticTip = \ Mark methods as static, whenever possible (devirtualization). method_marking_finalTip = \ Mark methods as final, whenever possible. method_marking_synchronizedTip = \ Unmark methods as synchronized, whenever possible. method_removal_parameterTip = \ Remove unused method parameters. method_generalization_classTip = \ Generalizes the classes of method invocations, whenever possible. method_specialization_parametertypeTip = \ Specializes the types of method parameters, whenever possible. method_specialization_returntypeTip = \ Specializes the types of method return values, whenever possible. method_propagation_parameterTip = \ Propagate the values of method parameters from method invocations to \ the invoked methods. method_propagation_returnvalueTip = \ Propagate the values of method return values from methods to their \ invocations. method_inlining_shortTip = \ Inline short methods. method_inlining_uniqueTip = \ Inline methods that are only called once. method_inlining_tailrecursionTip = \ Simplify tail recursion calls, whenever possible. code_mergingTip = \ Merge identical blocks of code by modifying branch targets. code_simplification_variableTip = \ Perform peephole optimizations for variable loading and storing. code_simplification_arithmeticTip = \ Perform peephole optimizations for arithmetic instructions. code_simplification_castTip = \ Perform peephole optimizations for casting operations. code_simplification_fieldTip = \ Perform peephole optimizations for field loading and storing. code_simplification_branchTip = \ Perform peephole optimizations for branch instructions. code_simplification_objectTip = \ Perform peephole optimizations for object instantiation. code_simplification_stringTip = \ Perform peephole optimizations for constant strings. code_simplification_mathTip = \ Perform peephole optimizations for Math method calls. code_simplification_advancedTip = \ Simplify code based on control flow analysis and data flow analysis. code_removal_advancedTip = \ Remove dead code based on control flow analysis and data flow analysis. code_removal_simpleTip = \ Remove dead code based on a simple control flow analysis. code_removal_variableTip = \ Remove unused variables from the local variable frame. code_removal_exceptionTip = \ Remove exceptions with empty try blocks. code_allocation_variableTip = \ Optimize variable allocation on the local variable frame. # # File selection titles. # selectConfigurationFile = Select a configuration file... saveConfigurationFile = Save configuration... selectUsageFile = Select a usage output file... selectPrintMappingFile = Select an output mapping file... selectApplyMappingFile = Select an input mapping file... selectObfuscationDictionaryFile = Select an obfuscation dictionary... selectSeedsFile = Select a seeds output file... selectDumpFile = Select a class dump file... selectStackTraceFile = Select a stack trace file... cantOpenConfigurationFile = Can''t open the configuration file [{0}] cantParseConfigurationFile = Can''t parse the configuration file [{0}] cantSaveConfigurationFile = Can''t save the configuration file [{0}] cantOpenStackTraceFile = Can''t open the stack trace file [{0}] jarExtensions = *.jar, *.aar, *.war, *.ear, *.jmod, *.zip, *.apk, *.ap_ (archives and directories) proExtension = *.pro (ProGuard configurations) addJars = Add one or more jars or directories... chooseJars = Choose different jars or directories... enterFilter = Optionally filter the file names contained in the selected entries. filters = Filters nameFilter = File name filter apkNameFilter = Apk name filter jarNameFilter = Jar name filter aarNameFilter = Aar name filter warNameFilter = War name filter earNameFilter = Ear name filter jmodNameFilter = Jmod name filter zipNameFilter = Zip name filter outputFileTip = The optional output file. inputFileTip = The input file. nameFilterTip = A filter on plain class file names and resource file names. apkNameFilterTip = A filter on apk file names. jarNameFilterTip = A filter on jar file names. aarNameFilterTip = A filter on aar file names. warNameFilterTip = A filter on war file names. earNameFilterTip = A filter on ear file names. jmodNameFilterTip = A filter on jmod file names. zipNameFilterTip = A filter on zip file names. # # Simple button texts. # previous = Previous next = Next browse = Browse... advanced = Advanced options basic = Basic options selectAll = Select all selectNone = Select none ok = Ok cancel = Cancel add = Add... addInput = Add input... addOutput = Add output... edit = Edit... filter = Filter... remove = Remove moveUp = Move up moveDown = Move down moveToLibraries = Move to libraries moveToProgram = Move to program addField = Add field... addMethod = Add method... select = Select... loadConfiguration = Load configuration... viewConfiguration = View configuration saveConfiguration = Save configuration... loadStackTrace = Load stack trace... process = Process! reTrace = ReTrace! advancedTip = Toggle between showing basic options and advanced options. addInputTip = Add an input jar, aar, war, ear, jmod, zip, apk, or directory. addOutputTip = Add an output jar, aar, war, ear, jmod, zip, apk, or directory. addTip = Add an entry. editTip = Edit the selected entries. filterTip = Put filters on the contents of the selected entries. removeTip = Remove the selected entries. moveUpTip = Move the selected entries up in the list. moveDownTip = Move the selected entries down in the list. moveToLibrariesTip = Move to selected entries to the libraries. moveToProgramTip = Move to selected entries to the program. addFieldTip = Add a field to the specification. addMethodTip = Add a method to the specification. loadConfigurationTip = Optionally load an initial configuration. viewConfigurationTip = View the current configuration. saveConfigurationTip = Save the current configuration. loadStackTraceTip = Load a stack trace from a file. processTip = Start processing, based on the current configuration. reTraceTip = De-obfuscate the given stack trace. # # Progress messages and error messages. # warning = Warning outOfMemory = Out of memory outOfMemoryInfo = \n\ You should run the ProGuard GUI with a larger java heap size, \ with a command like\ \n\n\t\ java -Xms128m -Xmx192m -jar proguardgui.jar {0}\ \n\n\ or you can try running ProGuard from the command line. \ with a command like\ \n\n\t\ java -jar proguard.jar @{0} sampleConfigurationFileName = configuration.pro errorProcessing = Error during processing errorReTracing = Error during retracing ================================================ FILE: gui/src/proguard/gui/JavaUtil.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.gui; import java.io.File; /** * @author James Hamilton */ public class JavaUtil { public static int currentJavaVersion() { String version = System.getProperty("java.version"); if (version.endsWith("-ea")) { version = version.substring(0, version.length() - 3); } if (version.startsWith("1.")) { version = version.substring(2, 3); } else { int dot = version.indexOf('.'); if (dot != -1) { version = version.substring(0, dot); } } return Integer.parseInt(version); } public static File getCurrentJavaHome() { if (currentJavaVersion() > 8) { return new File(System.getProperty("java.home")); } else { return new File(System.getProperty("java.home")).getParentFile(); } } public static File getRtJar() { File currentJavaHome = getCurrentJavaHome(); if (new File(currentJavaHome, "jre").exists()) { return new File(currentJavaHome, "jre" + File.separator + "lib" + File.separator + "rt.jar"); } else { return new File(currentJavaHome, "lib" + File.separator + "rt.jar"); } } public static File getJmodBase() { return new File(JavaUtil.getCurrentJavaHome(), "jmods" + File.separator + "java.base.jmod"); } } ================================================ FILE: gui/src/proguard/gui/KeepSpecificationsPanel.java ================================================ /* * 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.gui; import proguard.*; import javax.swing.*; /** * This ListPanel allows the user to add, edit, move, and remove * KeepClassSpecification entries in a list. * * @author Eric Lafortune */ final class KeepSpecificationsPanel extends ClassSpecificationsPanel { private final boolean markClasses; private final boolean markConditionally; private final boolean markDescriptorClasses; private final boolean allowShrinking; private final boolean allowOptimization; private final boolean allowObfuscation; public KeepSpecificationsPanel(JFrame owner, boolean markClasses, boolean markConditionally, boolean markDescriptorClasses, boolean allowShrinking, boolean allowOptimization, boolean allowObfuscation) { super(owner, true, true); this.markClasses = markClasses; this.markConditionally = markConditionally; this.markDescriptorClasses = markDescriptorClasses; this.allowShrinking = allowShrinking; this.allowOptimization = allowOptimization; this.allowObfuscation = allowObfuscation; } // Factory methods for ClassSpecificationsPanel. protected ClassSpecification createClassSpecification() { return new KeepClassSpecification(markClasses, markConditionally, markDescriptorClasses, false, false, allowShrinking, allowOptimization, allowObfuscation, null, super.createClassSpecification()); } protected void setClassSpecification(ClassSpecification classSpecification) { classSpecificationDialog.setKeepSpecification((KeepClassSpecification)classSpecification); } protected ClassSpecification getClassSpecification() { return classSpecificationDialog.getKeepSpecification(); } } ================================================ FILE: gui/src/proguard/gui/ListPanel.java ================================================ /* * 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.gui; import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.util.List; /** * This Jpanel allows the user to move and remove entries in a * list and between lists. Extensions of this class should add buttons to add * and possibly edit entries, and to set and get the resulting list. * * @author Eric Lafortune */ abstract class ListPanel extends JPanel { protected final DefaultListModel listModel = new DefaultListModel(); protected final JList list = new JList(listModel); protected int firstSelectionButton = 2; protected ListPanel() { GridBagLayout layout = new GridBagLayout(); setLayout(layout); GridBagConstraints listConstraints = new GridBagConstraints(); listConstraints.gridheight = GridBagConstraints.REMAINDER; listConstraints.fill = GridBagConstraints.BOTH; listConstraints.weightx = 1.0; listConstraints.weighty = 1.0; listConstraints.anchor = GridBagConstraints.NORTHWEST; listConstraints.insets = new Insets(0, 2, 0, 2); // Make sure some buttons are disabled or enabled depending on whether // the selection is empty or not. list.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { enableSelectionButtons(); } }); add(new JScrollPane(list), listConstraints); // something like the following calls are up to the extending class: //addAddButton(); //addEditButton(); //addRemoveButton(); //addUpButton(); //addDownButton(); // //enableSelectionButtons(); } protected void addRemoveButton() { JButton removeButton = new JButton(msg("remove")); removeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Remove the selected elements. removeElementsAt(list.getSelectedIndices()); } }); addButton(tip(removeButton, "removeTip")); } protected void addUpButton() { JButton upButton = new JButton(msg("moveUp")); upButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int[] selectedIndices = list.getSelectedIndices(); if (selectedIndices.length > 0 && selectedIndices[0] > 0) { // Move the selected elements up. moveElementsAt(selectedIndices, -1); } } }); addButton(tip(upButton, "moveUpTip")); } protected void addDownButton() { JButton downButton = new JButton(msg("moveDown")); downButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int[] selectedIndices = list.getSelectedIndices(); if (selectedIndices.length > 0 && selectedIndices[selectedIndices.length-1] < listModel.getSize()-1) { // Move the selected elements down. moveElementsAt(selectedIndices, 1); } } }); addButton(tip(downButton, "moveDownTip")); } /** * Adds a button that allows to copy or move entries to another ListPanel. * * @param buttonTextKey the button text key. * @param tipKey the tool tip key. * @param panel the other ListPanel. */ public void addCopyToPanelButton(String buttonTextKey, String tipKey, final ListPanel panel) { JButton moveButton = new JButton(msg(buttonTextKey)); moveButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int[] selectedIndices = list.getSelectedIndices(); Object[] selectedElements = list.getSelectedValues(); // Remove the selected elements from this panel. removeElementsAt(selectedIndices); // Add the elements to the other panel. panel.addElements(selectedElements); } }); addButton(tip(moveButton, tipKey)); } protected void addButton(JComponent button) { GridBagConstraints buttonConstraints = new GridBagConstraints(); buttonConstraints.gridwidth = GridBagConstraints.REMAINDER; buttonConstraints.fill = GridBagConstraints.HORIZONTAL; buttonConstraints.anchor = GridBagConstraints.NORTHWEST; buttonConstraints.insets = new Insets(0, 2, 0, 2); add(button, buttonConstraints); } /** * Returns a list of all right-hand side buttons. */ public List getButtons() { List list = new ArrayList(getComponentCount()-1); // Add all buttons. for (int index = 1; index < getComponentCount(); index++) { list.add(getComponent(index)); } return list; } protected void addElement(Object element) { listModel.addElement(element); // Make sure it is selected. list.setSelectedIndex(listModel.size() - 1); } protected void addElements(Object[] elements) { // Add the elements one by one. for (int index = 0; index < elements.length; index++) { listModel.addElement(elements[index]); } // Make sure they are selected. int[] selectedIndices = new int[elements.length]; for (int index = 0; index < selectedIndices.length; index++) { selectedIndices[index] = listModel.size() - selectedIndices.length + index; } list.setSelectedIndices(selectedIndices); } protected void moveElementsAt(int[] indices, int offset) { // Remember the selected elements. Object[] selectedElements = list.getSelectedValues(); // Remove the selected elements. removeElementsAt(indices); // Update the element indices. for (int index = 0; index < indices.length; index++) { indices[index] += offset; } // Reinsert the selected elements. insertElementsAt(selectedElements, indices); } protected void insertElementsAt(Object[] elements, int[] indices) { for (int index = 0; index < elements.length; index++) { listModel.insertElementAt(elements[index], indices[index]); } // Make sure they are selected. list.setSelectedIndices(indices); } protected void setElementAt(Object element, int index) { listModel.setElementAt(element, index); // Make sure it is selected. list.setSelectedIndex(index); } protected void setElementsAt(Object[] elements, int[] indices) { for (int index = 0; index < elements.length; index++) { listModel.setElementAt(elements[index], indices[index]); } // Make sure they are selected. list.setSelectedIndices(indices); } protected void removeElementsAt(int[] indices) { for (int index = indices.length - 1; index >= 0; index--) { listModel.removeElementAt(indices[index]); } // Make sure nothing is selected. list.clearSelection(); // Make sure the selection buttons are properly enabled, // since the above method doesn't seem to notify the listener. enableSelectionButtons(); } protected void removeAllElements() { listModel.removeAllElements(); // Make sure the selection buttons are properly enabled, // since the above method doesn't seem to notify the listener. enableSelectionButtons(); } /** * Enables or disables the buttons that depend on a selection. */ protected void enableSelectionButtons() { boolean selected = !list.isSelectionEmpty(); // Loop over all components, except the list itself and the Add button. for (int index = firstSelectionButton; index < getComponentCount(); index++) { getComponent(index).setEnabled(selected); } } /** * Attaches the tool tip from the GUI resources that corresponds to the * given key, to the given component. */ private static JComponent tip(JComponent component, String messageKey) { component.setToolTipText(msg(messageKey)); return component; } /** * Returns the message from the GUI resources that corresponds to the given * key. */ private static String msg(String messageKey) { return GUIResources.getMessage(messageKey); } } ================================================ FILE: gui/src/proguard/gui/MemberSpecificationDialog.java ================================================ /* * 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.gui; import proguard.MemberSpecification; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import proguard.util.ListUtil; import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*; /** * This JDialog allows the user to enter a String. * * @author Eric Lafortune */ final class MemberSpecificationDialog extends JDialog { /** * Return value if the dialog is canceled (with the Cancel button or by * closing the dialog window). */ public static final int CANCEL_OPTION = 1; /** * Return value if the dialog is approved (with the Ok button). */ public static final int APPROVE_OPTION = 0; private final boolean isField; private final JRadioButton[] publicRadioButtons; private final JRadioButton[] privateRadioButtons; private final JRadioButton[] protectedRadioButtons; private final JRadioButton[] staticRadioButtons; private final JRadioButton[] finalRadioButtons; private final JRadioButton[] syntheticRadioButtons; private JRadioButton[] volatileRadioButtons; private JRadioButton[] transientRadioButtons; private JRadioButton[] synchronizedRadioButtons; private JRadioButton[] nativeRadioButtons; private JRadioButton[] abstractRadioButtons; private JRadioButton[] strictRadioButtons; private JRadioButton[] bridgeRadioButtons; private JRadioButton[] varargsRadioButtons; private final JTextField annotationTypeTextField = new JTextField(20); private final JTextField nameTextField = new JTextField(20); private final JTextField typeTextField = new JTextField(20); private final JTextField argumentTypesTextField = new JTextField(20); private int returnValue; public MemberSpecificationDialog(JDialog owner, boolean isField) { super(owner, msg(isField ? "specifyFields" : "specifyMethods"), true); setResizable(true); // Create some constraints that can be reused. GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.insets = new Insets(1, 2, 1, 2); GridBagConstraints constraintsStretch = new GridBagConstraints(); constraintsStretch.fill = GridBagConstraints.HORIZONTAL; constraintsStretch.weightx = 1.0; constraintsStretch.anchor = GridBagConstraints.WEST; constraintsStretch.insets = constraints.insets; GridBagConstraints constraintsLast = new GridBagConstraints(); constraintsLast.gridwidth = GridBagConstraints.REMAINDER; constraintsLast.anchor = GridBagConstraints.WEST; constraintsLast.insets = constraints.insets; GridBagConstraints constraintsLastStretch = new GridBagConstraints(); constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER; constraintsLastStretch.fill = GridBagConstraints.HORIZONTAL; constraintsLastStretch.weightx = 1.0; constraintsLastStretch.anchor = GridBagConstraints.WEST; constraintsLastStretch.insets = constraints.insets; GridBagConstraints panelConstraints = new GridBagConstraints(); panelConstraints.gridwidth = GridBagConstraints.REMAINDER; panelConstraints.fill = GridBagConstraints.HORIZONTAL; panelConstraints.weightx = 1.0; panelConstraints.weighty = 0.0; panelConstraints.anchor = GridBagConstraints.NORTHWEST; panelConstraints.insets = constraints.insets; GridBagConstraints stretchPanelConstraints = new GridBagConstraints(); stretchPanelConstraints.gridwidth = GridBagConstraints.REMAINDER; stretchPanelConstraints.fill = GridBagConstraints.BOTH; stretchPanelConstraints.weightx = 1.0; stretchPanelConstraints.weighty = 1.0; stretchPanelConstraints.anchor = GridBagConstraints.NORTHWEST; stretchPanelConstraints.insets = constraints.insets; GridBagConstraints labelConstraints = new GridBagConstraints(); labelConstraints.anchor = GridBagConstraints.CENTER; labelConstraints.insets = new Insets(2, 10, 2, 10); GridBagConstraints lastLabelConstraints = new GridBagConstraints(); lastLabelConstraints.gridwidth = GridBagConstraints.REMAINDER; lastLabelConstraints.anchor = GridBagConstraints.CENTER; lastLabelConstraints.insets = labelConstraints.insets; GridBagConstraints advancedButtonConstraints = new GridBagConstraints(); advancedButtonConstraints.weightx = 1.0; advancedButtonConstraints.weighty = 1.0; advancedButtonConstraints.anchor = GridBagConstraints.SOUTHWEST; advancedButtonConstraints.insets = new Insets(4, 4, 8, 4); GridBagConstraints okButtonConstraints = new GridBagConstraints(); okButtonConstraints.weightx = 1.0; okButtonConstraints.weighty = 1.0; okButtonConstraints.anchor = GridBagConstraints.SOUTHEAST; okButtonConstraints.insets = advancedButtonConstraints.insets; GridBagConstraints cancelButtonConstraints = new GridBagConstraints(); cancelButtonConstraints.gridwidth = GridBagConstraints.REMAINDER; cancelButtonConstraints.weighty = 1.0; cancelButtonConstraints.anchor = GridBagConstraints.SOUTHEAST; cancelButtonConstraints.insets = okButtonConstraints.insets; GridBagLayout layout = new GridBagLayout(); Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED); this.isField = isField; // Create the access panel. JPanel accessPanel = new JPanel(layout); accessPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("access"))); accessPanel.add(Box.createGlue(), labelConstraints); accessPanel.add(tip(new JLabel(msg("required")), "requiredTip"), labelConstraints); accessPanel.add(tip(new JLabel(msg("not")), "notTip"), labelConstraints); accessPanel.add(tip(new JLabel(msg("dontCare")), "dontCareTip"), labelConstraints); accessPanel.add(Box.createGlue(), constraintsLastStretch); publicRadioButtons = addRadioButtonTriplet("Public", accessPanel); privateRadioButtons = addRadioButtonTriplet("Private", accessPanel); protectedRadioButtons = addRadioButtonTriplet("Protected", accessPanel); staticRadioButtons = addRadioButtonTriplet("Static", accessPanel); finalRadioButtons = addRadioButtonTriplet("Final", accessPanel); syntheticRadioButtons = addRadioButtonTriplet("Synthetic", accessPanel); if (isField) { volatileRadioButtons = addRadioButtonTriplet("Volatile", accessPanel); transientRadioButtons = addRadioButtonTriplet("Transient", accessPanel); } else { synchronizedRadioButtons = addRadioButtonTriplet("Synchronized", accessPanel); nativeRadioButtons = addRadioButtonTriplet("Native", accessPanel); abstractRadioButtons = addRadioButtonTriplet("Abstract", accessPanel); strictRadioButtons = addRadioButtonTriplet("Strict", accessPanel); bridgeRadioButtons = addRadioButtonTriplet("Bridge", accessPanel); varargsRadioButtons = addRadioButtonTriplet("Varargs", accessPanel); } // Create the type panel. JPanel typePanel = new JPanel(layout); typePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg(isField ? "fieldType" : "returnType"))); typePanel.add(tip(typeTextField, "typeTip"), constraintsLastStretch); // Create the annotation type panel. final JPanel annotationTypePanel = new JPanel(layout); annotationTypePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("annotation"))); annotationTypePanel.add(tip(annotationTypeTextField, "classNameTip"), constraintsLastStretch); // Create the name panel. JPanel namePanel = new JPanel(layout); namePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("name"))); namePanel.add(tip(nameTextField, isField ? "fieldNameTip" : "methodNameTip"), constraintsLastStretch); // Create the arguments panel. JPanel argumentsPanel = new JPanel(layout); argumentsPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg("argumentTypes"))); argumentsPanel.add(tip(argumentTypesTextField, "argumentTypes2Tip"), constraintsLastStretch); // Create the Advanced button. final JButton advancedButton = new JButton(msg("basic")); advancedButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { boolean visible = !annotationTypePanel.isVisible(); annotationTypePanel.setVisible(visible); advancedButton.setText(msg(visible ? "basic" : "advanced")); pack(); } }); advancedButton.doClick(); // Create the Ok button. JButton okButton = new JButton(msg("ok")); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { returnValue = APPROVE_OPTION; hide(); } }); // Create the Cancel button. JButton cancelButton = new JButton(msg("cancel")); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { hide(); } }); // Add all panels to the main panel. JPanel mainPanel = new JPanel(layout); mainPanel.add(tip(accessPanel, "accessTip"), panelConstraints); mainPanel.add(tip(annotationTypePanel, "annotationTip"), panelConstraints); mainPanel.add(tip(typePanel, isField ? "fieldTypeTip" : "returnTypeTip"), panelConstraints); mainPanel.add(tip(namePanel, "nameTip"), panelConstraints); if (!isField) { mainPanel.add(tip(argumentsPanel, "argumentTypesTip"), panelConstraints); } mainPanel.add(tip(advancedButton, "advancedTip"), advancedButtonConstraints); mainPanel.add(okButton, okButtonConstraints); mainPanel.add(cancelButton, cancelButtonConstraints); getContentPane().add(new JScrollPane(mainPanel)); } /** * Adds a JLabel and three JRadioButton instances in a ButtonGroup to the * given panel with a GridBagLayout, and returns the buttons in an array. */ private JRadioButton[] addRadioButtonTriplet(String labelText, JPanel panel) { GridBagConstraints labelConstraints = new GridBagConstraints(); labelConstraints.anchor = GridBagConstraints.WEST; labelConstraints.insets = new Insets(2, 10, 2, 10); GridBagConstraints buttonConstraints = new GridBagConstraints(); buttonConstraints.insets = labelConstraints.insets; GridBagConstraints lastGlueConstraints = new GridBagConstraints(); lastGlueConstraints.gridwidth = GridBagConstraints.REMAINDER; lastGlueConstraints.weightx = 1.0; // Create the radio buttons. JRadioButton radioButton0 = new JRadioButton(); JRadioButton radioButton1 = new JRadioButton(); JRadioButton radioButton2 = new JRadioButton(); // Put them in a button group. ButtonGroup buttonGroup = new ButtonGroup(); buttonGroup.add(radioButton0); buttonGroup.add(radioButton1); buttonGroup.add(radioButton2); // Add the label and the buttons to the panel. panel.add(new JLabel(labelText), labelConstraints); panel.add(radioButton0, buttonConstraints); panel.add(radioButton1, buttonConstraints); panel.add(radioButton2, buttonConstraints); panel.add(Box.createGlue(), lastGlueConstraints); return new JRadioButton[] { radioButton0, radioButton1, radioButton2 }; } /** * Sets the MemberSpecification to be represented in this dialog. */ public void setMemberSpecification(MemberSpecification memberSpecification) { String annotationType = memberSpecification.annotationType; String name = memberSpecification.name; String descriptor = memberSpecification.descriptor; // Set the class name text fields. annotationTypeTextField.setText(annotationType == null ? "" : ClassUtil.externalType(annotationType)); // Set the access radio buttons. setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.PUBLIC, publicRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.PRIVATE, privateRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.PROTECTED, protectedRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.STATIC, staticRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.FINAL, finalRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.SYNTHETIC, syntheticRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.VOLATILE, volatileRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.TRANSIENT, transientRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.SYNCHRONIZED, synchronizedRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.NATIVE, nativeRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.ABSTRACT, abstractRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.STRICT, strictRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.BRIDGE, bridgeRadioButtons); setMemberSpecificationRadioButtons(memberSpecification, AccessConstants.VARARGS, varargsRadioButtons); // Set the class name text fields. nameTextField.setText(name == null ? "*" : name); if (isField) { typeTextField .setText(descriptor == null ? "***" : ClassUtil.externalType(descriptor)); } else { typeTextField .setText(descriptor == null ? "***" : ClassUtil.externalMethodReturnType(descriptor)); argumentTypesTextField.setText(descriptor == null ? "..." : ClassUtil.externalMethodArguments(descriptor)); } } /** * Returns the MemberSpecification currently represented in this dialog. */ public MemberSpecification getMemberSpecification() { String annotationType = annotationTypeTextField.getText(); String name = nameTextField.getText(); String type = typeTextField.getText(); String arguments = argumentTypesTextField.getText(); // Convert all class member specifications into the internal format. annotationType = annotationType.equals("") || annotationType.equals("***") ? null : ClassUtil.internalType(annotationType); if (name.equals("") || name.equals("*")) { name = null; } if (isField) { type = type.equals("") || type.equals("***") ? null : ClassUtil.internalType(type); } else { if (type.equals("")) { type = JavaTypeConstants.VOID; } type = type .equals("***") && arguments.equals("...") ? null : ClassUtil.internalMethodDescriptor(type, ListUtil.commaSeparatedList(arguments)); } MemberSpecification memberSpecification = new MemberSpecification(0, 0, annotationType, name, type); // Also get the access radio button settings. getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.PUBLIC, publicRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.PRIVATE, privateRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.PROTECTED, protectedRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.STATIC, staticRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.FINAL, finalRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.SYNTHETIC, syntheticRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.VOLATILE, volatileRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.TRANSIENT, transientRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.SYNCHRONIZED, synchronizedRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.NATIVE, nativeRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.ABSTRACT, abstractRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.STRICT, strictRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.BRIDGE, bridgeRadioButtons); getMemberSpecificationRadioButtons(memberSpecification, AccessConstants.VARARGS, varargsRadioButtons); return memberSpecification; } /** * Shows this dialog. This method only returns when the dialog is closed. * * @return CANCEL_OPTION or APPROVE_OPTION, * depending on the choice of the user. */ public int showDialog() { returnValue = CANCEL_OPTION; // Open the dialog in the right place, then wait for it to be closed, // one way or another. pack(); setLocationRelativeTo(getOwner()); show(); return returnValue; } /** * Sets the appropriate radio button of a given triplet, based on the access * flags of the given keep option. */ private void setMemberSpecificationRadioButtons(MemberSpecification memberSpecification, int flag, JRadioButton[] radioButtons) { if (radioButtons != null) { int index = (memberSpecification.requiredSetAccessFlags & flag) != 0 ? 0 : (memberSpecification.requiredUnsetAccessFlags & flag) != 0 ? 1 : 2; radioButtons[index].setSelected(true); } } /** * Updates the access flag of the given keep option, based on the given radio * button triplet. */ private void getMemberSpecificationRadioButtons(MemberSpecification memberSpecification, int flag, JRadioButton[] radioButtons) { if (radioButtons != null) { if (radioButtons[0].isSelected()) { memberSpecification.requiredSetAccessFlags |= flag; } else if (radioButtons[1].isSelected()) { memberSpecification.requiredUnsetAccessFlags |= flag; } } } /** * Attaches the tool tip from the GUI resources that corresponds to the * given key, to the given component. */ private static JComponent tip(JComponent component, String messageKey) { component.setToolTipText(msg(messageKey)); return component; } /** * Returns the message from the GUI resources that corresponds to the given * key. */ private static String msg(String messageKey) { return GUIResources.getMessage(messageKey); } } ================================================ FILE: gui/src/proguard/gui/MemberSpecificationsPanel.java ================================================ /* * 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.gui; import proguard.MemberSpecification; import proguard.classfile.ClassConstants; import proguard.classfile.util.ClassUtil; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.util.List; /** * This ListPanel allows the user to add, edit, move, and remove * MemberSpecification entries in a list. * * @author Eric Lafortune */ final class MemberSpecificationsPanel extends ListPanel { private final MemberSpecificationDialog fieldSpecificationDialog; private final MemberSpecificationDialog methodSpecificationDialog; public MemberSpecificationsPanel(JDialog owner, boolean includeFieldButton) { super(); super.firstSelectionButton = includeFieldButton ? 3 : 2; list.setCellRenderer(new MyListCellRenderer()); fieldSpecificationDialog = new MemberSpecificationDialog(owner, true); methodSpecificationDialog = new MemberSpecificationDialog(owner, false); if (includeFieldButton) { addAddFieldButton(); } addAddMethodButton(); addEditButton(); addRemoveButton(); addUpButton(); addDownButton(); enableSelectionButtons(); } protected void addAddFieldButton() { JButton addFieldButton = new JButton(msg("addField")); addFieldButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fieldSpecificationDialog.setMemberSpecification(new MemberSpecification()); int returnValue = fieldSpecificationDialog.showDialog(); if (returnValue == MemberSpecificationDialog.APPROVE_OPTION) { // Add the new element. addElement(new MyMemberSpecificationWrapper(fieldSpecificationDialog.getMemberSpecification(), true)); } } }); addButton(tip(addFieldButton, "addFieldTip")); } protected void addAddMethodButton() { JButton addMethodButton = new JButton(msg("addMethod")); addMethodButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { methodSpecificationDialog.setMemberSpecification(new MemberSpecification()); int returnValue = methodSpecificationDialog.showDialog(); if (returnValue == MemberSpecificationDialog.APPROVE_OPTION) { // Add the new element. addElement(new MyMemberSpecificationWrapper(methodSpecificationDialog.getMemberSpecification(), false)); } } }); addButton(tip(addMethodButton, "addMethodTip")); } protected void addEditButton() { JButton editButton = new JButton(msg("edit")); editButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { MyMemberSpecificationWrapper wrapper = (MyMemberSpecificationWrapper)list.getSelectedValue(); MemberSpecificationDialog memberSpecificationDialog = wrapper.isField ? fieldSpecificationDialog : methodSpecificationDialog; memberSpecificationDialog.setMemberSpecification(wrapper.memberSpecification); int returnValue = memberSpecificationDialog.showDialog(); if (returnValue == MemberSpecificationDialog.APPROVE_OPTION) { // Replace the old element. wrapper.memberSpecification = memberSpecificationDialog.getMemberSpecification(); setElementAt(wrapper, list.getSelectedIndex()); } } }); addButton(tip(editButton, "editTip")); } /** * Sets the MemberSpecification instances to be represented in this panel. */ public void setMemberSpecifications(List fieldSpecifications, List methodSpecifications) { listModel.clear(); if (fieldSpecifications != null) { for (int index = 0; index < fieldSpecifications.size(); index++) { listModel.addElement( new MyMemberSpecificationWrapper((MemberSpecification)fieldSpecifications.get(index), true)); } } if (methodSpecifications != null) { for (int index = 0; index < methodSpecifications.size(); index++) { listModel.addElement( new MyMemberSpecificationWrapper((MemberSpecification)methodSpecifications.get(index), false)); } } // Make sure the selection buttons are properly enabled, // since the clear method doesn't seem to notify the listener. enableSelectionButtons(); } /** * Returns the MemberSpecification instances currently represented in * this panel, referring to fields or to methods. * * @param isField specifies whether specifications referring to fields or * specifications referring to methods should be returned. */ public List getMemberSpecifications(boolean isField) { int size = listModel.size(); if (size == 0) { return null; } List memberSpecifications = new ArrayList(size); for (int index = 0; index < size; index++) { MyMemberSpecificationWrapper wrapper = (MyMemberSpecificationWrapper)listModel.get(index); if (wrapper.isField == isField) { memberSpecifications.add(wrapper.memberSpecification); } } return memberSpecifications; } /** * This ListCellRenderer renders MemberSpecification objects. */ private static class MyListCellRenderer implements ListCellRenderer { private final JLabel label = new JLabel(); // Implementations for ListCellRenderer. public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { MyMemberSpecificationWrapper wrapper = (MyMemberSpecificationWrapper)value; MemberSpecification option = wrapper.memberSpecification; String name = option.name; String descriptor = option.descriptor; label.setText(wrapper.isField ? (descriptor == null ? name == null ? "" : "***" + ' ' + name : ClassUtil.externalFullFieldDescription(0, name == null ? "*" : name, descriptor)) : (descriptor == null ? name == null ? "" : "***" + ' ' + name + "(...)" : ClassUtil.externalFullMethodDescription(ClassConstants.METHOD_NAME_INIT, 0, name == null ? "*" : name, descriptor))); if (isSelected) { label.setBackground(list.getSelectionBackground()); label.setForeground(list.getSelectionForeground()); } else { label.setBackground(list.getBackground()); label.setForeground(list.getForeground()); } label.setOpaque(true); return label; } } /** * Attaches the tool tip from the GUI resources that corresponds to the * given key, to the given component. */ private static JComponent tip(JComponent component, String messageKey) { component.setToolTipText(msg(messageKey)); return component; } /** * Returns the message from the GUI resources that corresponds to the given * key. */ private static String msg(String messageKey) { return GUIResources.getMessage(messageKey); } /** * This class wraps a MemberSpecification, additionally storing whether * the option refers to a field or to a method. */ private static class MyMemberSpecificationWrapper { public MemberSpecification memberSpecification; public final boolean isField; public MyMemberSpecificationWrapper(MemberSpecification memberSpecification, boolean isField) { this.memberSpecification = memberSpecification; this.isField = isField; } } } ================================================ FILE: gui/src/proguard/gui/MessageDialogRunnable.java ================================================ /* * 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.gui; import javax.swing.*; import java.awt.*; /** * This Runnable can show a message dialog. * * @author Eric Lafortune */ final class MessageDialogRunnable implements Runnable { private final Component parentComponent; private final Object message; private final String title; private final int messageType; /** * Creates a new MessageDialogRunnable object. * @see JOptionPane#showMessageDialog(Component, Object, String, int) */ public static void showMessageDialog(Component parentComponent, Object message, String title, int messageType) { try { SwingUtil.invokeAndWait(new MessageDialogRunnable(parentComponent, message, title, messageType)); } catch (Exception e) { // Nothing. } } /** * Creates a new MessageDialogRunnable object. * @see JOptionPane#showMessageDialog(Component, Object, String, int) */ public MessageDialogRunnable(Component parentComponent, Object message, String title, int messageType) { this.parentComponent = parentComponent; this.message = message; this.title = title; this.messageType = messageType; } // Implementation for Runnable. public void run() { JOptionPane.showMessageDialog(parentComponent, message, title, messageType); } } ================================================ FILE: gui/src/proguard/gui/OptimizationsDialog.java ================================================ /* * 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.gui; import proguard.optimize.Optimizer; import proguard.util.*; import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*; /** * This JDialog allows the user to enter a String. * * @author Eric Lafortune */ final class OptimizationsDialog extends JDialog { /** * Return value if the dialog is canceled (with the Cancel button or by * closing the dialog window). */ public static final int CANCEL_OPTION = 1; /** * Return value if the dialog is approved (with the Ok button). */ public static final int APPROVE_OPTION = 0; private final JCheckBox[] optimizationCheckBoxes = new JCheckBox[Optimizer.OPTIMIZATION_NAMES.length]; private int returnValue; public OptimizationsDialog(JFrame owner) { super(owner, msg("selectOptimizations"), true); setResizable(true); // Create some constraints that can be reused. GridBagConstraints constraintsLast = new GridBagConstraints(); constraintsLast.gridwidth = GridBagConstraints.REMAINDER; constraintsLast.anchor = GridBagConstraints.WEST; constraintsLast.insets = new Insets(1, 2, 1, 2); GridBagConstraints constraintsLastStretch = new GridBagConstraints(); constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER; constraintsLastStretch.fill = GridBagConstraints.HORIZONTAL; constraintsLastStretch.weightx = 1.0; constraintsLastStretch.anchor = GridBagConstraints.WEST; constraintsLastStretch.insets = constraintsLast.insets; GridBagConstraints panelConstraints = new GridBagConstraints(); panelConstraints.gridwidth = GridBagConstraints.REMAINDER; panelConstraints.fill = GridBagConstraints.HORIZONTAL; panelConstraints.weightx = 1.0; panelConstraints.weighty = 0.0; panelConstraints.anchor = GridBagConstraints.NORTHWEST; panelConstraints.insets = constraintsLast.insets; GridBagConstraints selectButtonConstraints = new GridBagConstraints(); selectButtonConstraints.weighty = 1.0; selectButtonConstraints.anchor = GridBagConstraints.SOUTHWEST; selectButtonConstraints.insets = new Insets(4, 4, 8, 4); GridBagConstraints okButtonConstraints = new GridBagConstraints(); okButtonConstraints.weightx = 1.0; okButtonConstraints.weighty = 1.0; okButtonConstraints.anchor = GridBagConstraints.SOUTHEAST; okButtonConstraints.insets = selectButtonConstraints.insets; GridBagConstraints cancelButtonConstraints = new GridBagConstraints(); cancelButtonConstraints.gridwidth = GridBagConstraints.REMAINDER; cancelButtonConstraints.weighty = 1.0; cancelButtonConstraints.anchor = GridBagConstraints.SOUTHEAST; cancelButtonConstraints.insets = selectButtonConstraints.insets; GridBagLayout layout = new GridBagLayout(); Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED); // Create the optimizations panel. JPanel optimizationsPanel = new JPanel(layout); JPanel optimizationSubpanel = null; String lastOptimizationPrefix = null; for (int index = 0; index < Optimizer.OPTIMIZATION_NAMES.length; index++) { String optimizationName = Optimizer.OPTIMIZATION_NAMES[index]; String optimizationPrefix = optimizationName.substring(0, optimizationName.indexOf('/')); if (optimizationSubpanel == null || !optimizationPrefix.equals(lastOptimizationPrefix)) { // Create a new keep subpanel and add it. optimizationSubpanel = new JPanel(layout); optimizationSubpanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg(optimizationPrefix))); optimizationsPanel.add(optimizationSubpanel, panelConstraints); lastOptimizationPrefix = optimizationPrefix; } JCheckBox optimizationCheckBox = new JCheckBox(optimizationName); optimizationCheckBoxes[index] = optimizationCheckBox; optimizationSubpanel.add(tip(optimizationCheckBox, optimizationName.replace('/', '_')+"Tip"), constraintsLastStretch); } // Create the Select All button. JButton selectAllButton = new JButton(msg("selectAll")); selectAllButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for (int index = 0; index < optimizationCheckBoxes.length; index++) { optimizationCheckBoxes[index].setSelected(true); } } }); // Create the Select All button. JButton selectNoneButton = new JButton(msg("selectNone")); selectNoneButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for (int index = 0; index < optimizationCheckBoxes.length; index++) { optimizationCheckBoxes[index].setSelected(false); } } }); // Create the Ok button. JButton okButton = new JButton(msg("ok")); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { returnValue = APPROVE_OPTION; hide(); } }); // Create the Cancel button. JButton cancelButton = new JButton(msg("cancel")); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { hide(); } }); // Add all panels to the main panel. optimizationsPanel.add(selectAllButton, selectButtonConstraints); optimizationsPanel.add(selectNoneButton, selectButtonConstraints); optimizationsPanel.add(okButton, okButtonConstraints); optimizationsPanel.add(cancelButton, cancelButtonConstraints); getContentPane().add(new JScrollPane(optimizationsPanel)); } /** * Sets the initial optimization filter to be used by the dialog. */ public void setFilter(String optimizations) { StringMatcher filter = optimizations != null && optimizations.length() > 0 ? new ListParser(new NameParser()).parse(optimizations) : new FixedStringMatcher(""); for (int index = 0; index < Optimizer.OPTIMIZATION_NAMES.length; index++) { optimizationCheckBoxes[index].setSelected(filter.matches(Optimizer.OPTIMIZATION_NAMES[index])); } } /** * Returns the optimization filter composed from the settings in the dialog. */ public String getFilter() { return new FilterBuilder(optimizationCheckBoxes, '/').buildFilter(); } /** * Shows this dialog. This method only returns when the dialog is closed. * * @return CANCEL_OPTION or APPROVE_OPTION, * depending on the choice of the user. */ public int showDialog() { returnValue = CANCEL_OPTION; // Open the dialog in the right place, then wait for it to be closed, // one way or another. pack(); setLocationRelativeTo(getOwner()); show(); return returnValue; } /** * Attaches the tool tip from the GUI resources that corresponds to the * given key, to the given component. */ private static JComponent tip(JComponent component, String messageKey) { component.setToolTipText(msg(messageKey)); return component; } /** * Returns the message from the GUI resources that corresponds to the given * key. */ private static String msg(String messageKey) { return GUIResources.getMessage(messageKey); } } ================================================ FILE: gui/src/proguard/gui/ProGuardGUI.java ================================================ /* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2022 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.gui; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.OutputStreamAppender; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.layout.PatternLayout; import proguard.*; import proguard.classfile.util.ClassUtil; import proguard.gui.splash.*; import proguard.util.ListUtil; import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.util.*; import java.util.List; import java.util.prefs.Preferences; /** * GUI for configuring and executing ProGuard and ReTrace. * * @author Eric Lafortune */ public class ProGuardGUI extends JFrame { private static final String NO_SPLASH_OPTION = "-nosplash"; private static final String TITLE_IMAGE_FILE = "vtitle.png"; private static final String BOILERPLATE_CONFIGURATION = "boilerplate.pro"; private static final String DEFAULT_CONFIGURATION = "default.pro"; private static final String OPTIMIZATIONS_DEFAULT = "*"; private static final String KEEP_ATTRIBUTE_DEFAULT = "Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod"; private static final String SOURCE_FILE_ATTRIBUTE_DEFAULT = "SourceFile"; private static final String ADAPT_RESOURCE_FILE_NAMES_DEFAULT = "**.properties"; private static final String ADAPT_RESOURCE_FILE_CONTENTS_DEFAULT = "**.properties,META-INF/MANIFEST.MF"; private static final String ELLIPSIS_DOTS = "..."; private static final Border BORDER = BorderFactory.createEtchedBorder(EtchedBorder.RAISED); static boolean systemOutRedirected; private static final Preferences PREFS = Preferences.userRoot().node(ProGuardGUI.class.getName()); private final JFileChooser configurationChooser = new JFileChooser(""); private final JFileChooser fileChooser = new JFileChooser(""); private final SplashPanel splashPanel; private final ClassPathPanel programPanel = new ClassPathPanel(this, true); private final ClassPathPanel libraryPanel = new ClassPathPanel(this, false); private KeepClassSpecification[] boilerplateKeep; private final JCheckBox[] boilerplateKeepCheckBoxes; private final JTextField[] boilerplateKeepTextFields; private final KeepSpecificationsPanel additionalKeepPanel = new KeepSpecificationsPanel(this, true, false, false, false, false, false); private KeepClassSpecification[] boilerplateKeepNames; private final JCheckBox[] boilerplateKeepNamesCheckBoxes; private final JTextField[] boilerplateKeepNamesTextFields; private final KeepSpecificationsPanel additionalKeepNamesPanel = new KeepSpecificationsPanel(this, true, false, false, true, false, false); private ClassSpecification[] boilerplateNoSideEffectMethods; private final JCheckBox[] boilerplateNoSideEffectMethodCheckBoxes; private final ClassSpecificationsPanel additionalNoSideEffectsPanel = new ClassSpecificationsPanel(this, false, false); private final ClassSpecificationsPanel whyAreYouKeepingPanel = new ClassSpecificationsPanel(this, false, true); private final JCheckBox shrinkCheckBox = new JCheckBox(msg("shrink")); private final JCheckBox printUsageCheckBox = new JCheckBox(msg("printUsage")); private final JCheckBox optimizeCheckBox = new JCheckBox(msg("optimize")); private final JCheckBox allowAccessModificationCheckBox = new JCheckBox(msg("allowAccessModification")); private final JCheckBox mergeInterfacesAggressivelyCheckBox = new JCheckBox(msg("mergeInterfacesAggressively")); private final JLabel optimizationsLabel = new JLabel(msg("optimizations")); private final JLabel optimizationPassesLabel = new JLabel(msg("optimizationPasses")); private final JSpinner optimizationPassesSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 9, 1)); private final JCheckBox obfuscateCheckBox = new JCheckBox(msg("obfuscate")); private final JCheckBox printMappingCheckBox = new JCheckBox(msg("printMapping")); private final JCheckBox applyMappingCheckBox = new JCheckBox(msg("applyMapping")); private final JCheckBox obfuscationDictionaryCheckBox = new JCheckBox(msg("obfuscationDictionary")); private final JCheckBox classObfuscationDictionaryCheckBox = new JCheckBox(msg("classObfuscationDictionary")); private final JCheckBox packageObfuscationDictionaryCheckBox = new JCheckBox(msg("packageObfuscationDictionary")); private final JCheckBox overloadAggressivelyCheckBox = new JCheckBox(msg("overloadAggressively")); private final JCheckBox useUniqueClassMemberNamesCheckBox = new JCheckBox(msg("useUniqueClassMemberNames")); private final JCheckBox useMixedCaseClassNamesCheckBox = new JCheckBox(msg("useMixedCaseClassNames")); private final JCheckBox keepPackageNamesCheckBox = new JCheckBox(msg("keepPackageNames")); private final JCheckBox flattenPackageHierarchyCheckBox = new JCheckBox(msg("flattenPackageHierarchy")); private final JCheckBox repackageClassesCheckBox = new JCheckBox(msg("repackageClasses")); private final JCheckBox keepAttributesCheckBox = new JCheckBox(msg("keepAttributes")); private final JCheckBox keepParameterNamesCheckBox = new JCheckBox(msg("keepParameterNames")); private final JCheckBox newSourceFileAttributeCheckBox = new JCheckBox(msg("renameSourceFileAttribute")); private final JCheckBox adaptClassStringsCheckBox = new JCheckBox(msg("adaptClassStrings")); private final JCheckBox adaptResourceFileNamesCheckBox = new JCheckBox(msg("adaptResourceFileNames")); private final JCheckBox adaptResourceFileContentsCheckBox = new JCheckBox(msg("adaptResourceFileContents")); private final JCheckBox preverifyCheckBox = new JCheckBox(msg("preverify")); private final JCheckBox microEditionCheckBox = new JCheckBox(msg("microEdition")); private final JCheckBox androidCheckBox = new JCheckBox(msg("android")); private final JCheckBox targetCheckBox = new JCheckBox(msg("target")); private final JComboBox targetComboBox = new JComboBox(ListUtil.commaSeparatedList(msg("targets")).toArray()); private final JCheckBox verboseCheckBox = new JCheckBox(msg("verbose")); private final JCheckBox noteCheckBox = new JCheckBox(msg("note")); private final JCheckBox warnCheckBox = new JCheckBox(msg("warn")); private final JCheckBox ignoreWarningsCheckBox = new JCheckBox(msg("ignoreWarnings")); private final JCheckBox skipNonPublicLibraryClassesCheckBox = new JCheckBox(msg("skipNonPublicLibraryClasses")); private final JCheckBox skipNonPublicLibraryClassMembersCheckBox = new JCheckBox(msg("skipNonPublicLibraryClassMembers")); private final JCheckBox keepDirectoriesCheckBox = new JCheckBox(msg("keepDirectories")); private final JCheckBox forceProcessingCheckBox = new JCheckBox(msg("forceProcessing")); private final JCheckBox printSeedsCheckBox = new JCheckBox(msg("printSeeds")); private final JCheckBox printConfigurationCheckBox = new JCheckBox(msg("printConfiguration")); private final JCheckBox dumpCheckBox = new JCheckBox(msg("dump")); private final JTextField printUsageTextField = new JTextField(40); private final JTextField optimizationsTextField = new JTextField(40); private final JTextField printMappingTextField = new JTextField(40); private final JTextField applyMappingTextField = new JTextField(40); private final JTextField obfuscationDictionaryTextField = new JTextField(40); private final JTextField classObfuscationDictionaryTextField = new JTextField(40); private final JTextField packageObfuscationDictionaryTextField = new JTextField(40); private final JTextField keepPackageNamesTextField = new JTextField(40); private final JTextField flattenPackageHierarchyTextField = new JTextField(40); private final JTextField repackageClassesTextField = new JTextField(40); private final JTextField keepAttributesTextField = new JTextField(40); private final JTextField newSourceFileAttributeTextField = new JTextField(40); private final JTextField adaptClassStringsTextField = new JTextField(40); private final JTextField adaptResourceFileNamesTextField = new JTextField(40); private final JTextField adaptResourceFileContentsTextField = new JTextField(40); private final JTextField noteTextField = new JTextField(40); private final JTextField warnTextField = new JTextField(40); private final JTextField keepDirectoriesTextField = new JTextField(40); private final JTextField printSeedsTextField = new JTextField(40); private final JTextField printConfigurationTextField = new JTextField(40); private final JTextField dumpTextField = new JTextField(40); private final JTextArea consoleTextArea = new JTextArea(msg("processingInfo"), 3, 40); private final JCheckBox reTraceVerboseCheckBox = new JCheckBox(msg("verbose")); private final JTextField reTraceMappingTextField = new JTextField(40); private final JTextArea stackTraceTextArea = new JTextArea(3, 40); private final JTextArea reTraceTextArea = new JTextArea(msg("reTraceInfo"), 3, 40); /** * Creates a new ProGuardGUI. */ public ProGuardGUI() { setTitle("ProGuard"); setDefaultCloseOperation(EXIT_ON_CLOSE); // Create some constraints that can be reused. GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.insets = new Insets(0, 4, 0, 4); GridBagConstraints constraintsStretch = new GridBagConstraints(); constraintsStretch.fill = GridBagConstraints.HORIZONTAL; constraintsStretch.weightx = 1.0; constraintsStretch.anchor = GridBagConstraints.WEST; constraintsStretch.insets = constraints.insets; GridBagConstraints constraintsLast = new GridBagConstraints(); constraintsLast.gridwidth = GridBagConstraints.REMAINDER; constraintsLast.anchor = GridBagConstraints.WEST; constraintsLast.insets = constraints.insets; GridBagConstraints constraintsLastStretch = new GridBagConstraints(); constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER; constraintsLastStretch.fill = GridBagConstraints.HORIZONTAL; constraintsLastStretch.weightx = 1.0; constraintsLastStretch.anchor = GridBagConstraints.WEST; constraintsLastStretch.insets = constraints.insets; GridBagConstraints splashPanelConstraints = new GridBagConstraints(); splashPanelConstraints.gridwidth = GridBagConstraints.REMAINDER; splashPanelConstraints.fill = GridBagConstraints.BOTH; splashPanelConstraints.weightx = 1.0; splashPanelConstraints.weighty = 0.02; splashPanelConstraints.anchor = GridBagConstraints.NORTHWEST; //splashPanelConstraints.insets = constraints.insets; GridBagConstraints welcomePaneConstraints = new GridBagConstraints(); welcomePaneConstraints.gridwidth = GridBagConstraints.REMAINDER; welcomePaneConstraints.fill = GridBagConstraints.NONE; welcomePaneConstraints.weightx = 1.0; welcomePaneConstraints.weighty = 0.01; welcomePaneConstraints.anchor = GridBagConstraints.CENTER;//NORTHWEST; welcomePaneConstraints.insets = new Insets(20, 40, 20, 40); GridBagConstraints panelConstraints = new GridBagConstraints(); panelConstraints.gridwidth = GridBagConstraints.REMAINDER; panelConstraints.fill = GridBagConstraints.HORIZONTAL; panelConstraints.weightx = 1.0; panelConstraints.anchor = GridBagConstraints.NORTHWEST; panelConstraints.insets = constraints.insets; GridBagConstraints stretchPanelConstraints = new GridBagConstraints(); stretchPanelConstraints.gridwidth = GridBagConstraints.REMAINDER; stretchPanelConstraints.fill = GridBagConstraints.BOTH; stretchPanelConstraints.weightx = 1.0; stretchPanelConstraints.weighty = 1.0; stretchPanelConstraints.anchor = GridBagConstraints.NORTHWEST; stretchPanelConstraints.insets = constraints.insets; GridBagConstraints containerConstraints = new GridBagConstraints(); containerConstraints.gridwidth = GridBagConstraints.REMAINDER; containerConstraints.fill = GridBagConstraints.BOTH; containerConstraints.weightx = 1.0; containerConstraints.weighty = 1.0; containerConstraints.anchor = GridBagConstraints.NORTHWEST; GridBagConstraints glueConstraints = new GridBagConstraints(); glueConstraints.fill = GridBagConstraints.BOTH; glueConstraints.weightx = 0.01; glueConstraints.weighty = 0.01; glueConstraints.anchor = GridBagConstraints.NORTHWEST; glueConstraints.insets = constraints.insets; GridBagConstraints bottomButtonConstraints = new GridBagConstraints(); bottomButtonConstraints.anchor = GridBagConstraints.SOUTHEAST; bottomButtonConstraints.insets = new Insets(2, 2, 4, 6); bottomButtonConstraints.ipadx = 10; bottomButtonConstraints.ipady = 2; GridBagConstraints lastBottomButtonConstraints = new GridBagConstraints(); lastBottomButtonConstraints.gridwidth = GridBagConstraints.REMAINDER; lastBottomButtonConstraints.anchor = GridBagConstraints.SOUTHEAST; lastBottomButtonConstraints.insets = bottomButtonConstraints.insets; lastBottomButtonConstraints.ipadx = bottomButtonConstraints.ipadx; lastBottomButtonConstraints.ipady = bottomButtonConstraints.ipady; // Leave room for a growBox on Mac OS X. if (System.getProperty("os.name").toLowerCase().startsWith("mac os x")) { lastBottomButtonConstraints.insets = new Insets(2, 2, 4, 6 + 16); } GridBagLayout layout = new GridBagLayout(); configurationChooser.addChoosableFileFilter( new ExtensionFileFilter(msg("proExtension"), new String[] { ".pro" })); // Create the opening panel. Sprite splash = new CompositeSprite(new Sprite[] { new ColorSprite(new ConstantColor(Color.gray), new FontSprite(new ConstantFont(new Font("sansserif", Font.BOLD, 90)), new TextSprite(new ConstantString("ProGuard"), new ConstantInt(160), new LinearInt(-10, 120, new SmoothTiming(500, 1000))))), new ColorSprite(new ConstantColor(Color.white), new FontSprite(new ConstantFont(new Font("sansserif", Font.BOLD, 45)), new ShadowedSprite(new ConstantInt(3), new ConstantInt(3), new ConstantDouble(0.4), new ConstantInt(1), new CompositeSprite(new Sprite[] { new TextSprite(new ConstantString(msg("shrinking")), new LinearInt(1000, 60, new SmoothTiming(1000, 2000)), new ConstantInt(70)), new TextSprite(new ConstantString(msg("optimization")), new LinearInt(1000, 400, new SmoothTiming(1500, 2500)), new ConstantInt(60)), new TextSprite(new ConstantString(msg("obfuscation")), new LinearInt(1000, 10, new SmoothTiming(2000, 3000)), new ConstantInt(145)), new TextSprite(new ConstantString(msg("preverification")), new LinearInt(1000, 350, new SmoothTiming(2500, 3500)), new ConstantInt(140)), new FontSprite(new ConstantFont(new Font("sansserif", Font.BOLD, 30)), new TextSprite(new TypeWriterString(msg("developed"), new LinearTiming(3500, 5500)), new ConstantInt(250), new ConstantInt(200))), })))), }); splashPanel = new SplashPanel(splash, 0.5, 5500L); splashPanel.setPreferredSize(new Dimension(0, 200)); JEditorPane welcomePane = new JEditorPane("text/html", msg("proGuardInfo")); welcomePane.setPreferredSize(new Dimension(640, 350)); // The constant HONOR_DISPLAY_PROPERTIES isn't present yet in JDK 1.4. //welcomePane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); welcomePane.putClientProperty("JEditorPane.honorDisplayProperties", Boolean.TRUE); welcomePane.setOpaque(false); welcomePane.setEditable(false); welcomePane.setBorder(new EmptyBorder(20, 20, 20, 20)); addBorder(welcomePane, "welcome"); JPanel proGuardPanel = new JPanel(layout); proGuardPanel.add(splashPanel, splashPanelConstraints); proGuardPanel.add(welcomePane, welcomePaneConstraints); // Create the input panel. // TODO: properly clone the ClassPath objects. // This is awkward to implement in the generic ListPanel.addElements(...) // method, since the Object.clone() method is not public. programPanel.addCopyToPanelButton("moveToLibraries", "moveToLibrariesTip", libraryPanel); libraryPanel.addCopyToPanelButton("moveToProgram", "moveToProgramTip", programPanel); // Collect all buttons of these panels and make sure they are equally // sized. List panelButtons = new ArrayList(); panelButtons.addAll(programPanel.getButtons()); panelButtons.addAll(libraryPanel.getButtons()); setCommonPreferredSize(panelButtons); addBorder(programPanel, "programJars" ); addBorder(libraryPanel, "libraryJars" ); JPanel inputOutputPanel = new JPanel(layout); inputOutputPanel.add(tip(programPanel, "programJarsTip"), stretchPanelConstraints); inputOutputPanel.add(tip(libraryPanel, "libraryJarsTip"), stretchPanelConstraints); // Load the boiler plate options. loadBoilerplateConfiguration(); // Create the boiler plate keep panels. boilerplateKeepCheckBoxes = new JCheckBox[boilerplateKeep.length]; boilerplateKeepTextFields = new JTextField[boilerplateKeep.length]; JButton printUsageBrowseButton = createBrowseButton(printUsageTextField, msg("selectUsageFile")); JPanel shrinkingOptionsPanel = new JPanel(layout); addBorder(shrinkingOptionsPanel, "options"); shrinkingOptionsPanel.add(tip(shrinkCheckBox, "shrinkTip"), constraintsLastStretch); shrinkingOptionsPanel.add(tip(printUsageCheckBox, "printUsageTip"), constraints); shrinkingOptionsPanel.add(tip(printUsageTextField, "outputFileTip"), constraintsStretch); shrinkingOptionsPanel.add(tip(printUsageBrowseButton, "selectUsageFile"), constraintsLast); JPanel shrinkingPanel = new JPanel(layout); shrinkingPanel.add(shrinkingOptionsPanel, panelConstraints); addClassSpecifications(extractClassSpecifications(boilerplateKeep), boilerplateKeepCheckBoxes, boilerplateKeepTextFields, shrinkingPanel); addBorder(additionalKeepPanel, "keepAdditional"); shrinkingPanel.add(tip(additionalKeepPanel, "keepAdditionalTip"), stretchPanelConstraints); // Create the boiler plate keep names panels. boilerplateKeepNamesCheckBoxes = new JCheckBox[boilerplateKeepNames.length]; boilerplateKeepNamesTextFields = new JTextField[boilerplateKeepNames.length]; JButton printMappingBrowseButton = createBrowseButton(printMappingTextField, msg("selectPrintMappingFile")); JButton applyMappingBrowseButton = createBrowseButton(applyMappingTextField, msg("selectApplyMappingFile")); JButton obfucationDictionaryBrowseButton = createBrowseButton(obfuscationDictionaryTextField, msg("selectObfuscationDictionaryFile")); JButton classObfucationDictionaryBrowseButton = createBrowseButton(classObfuscationDictionaryTextField, msg("selectObfuscationDictionaryFile")); JButton packageObfucationDictionaryBrowseButton = createBrowseButton(packageObfuscationDictionaryTextField, msg("selectObfuscationDictionaryFile")); JPanel obfuscationOptionsPanel = new JPanel(layout); addBorder(obfuscationOptionsPanel, "options"); obfuscationOptionsPanel.add(tip(obfuscateCheckBox, "obfuscateTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(printMappingCheckBox, "printMappingTip"), constraints); obfuscationOptionsPanel.add(tip(printMappingTextField, "outputFileTip"), constraintsStretch); obfuscationOptionsPanel.add(tip(printMappingBrowseButton, "selectPrintMappingFile"), constraintsLast); obfuscationOptionsPanel.add(tip(applyMappingCheckBox, "applyMappingTip"), constraints); obfuscationOptionsPanel.add(tip(applyMappingTextField, "inputFileTip"), constraintsStretch); obfuscationOptionsPanel.add(tip(applyMappingBrowseButton, "selectApplyMappingFile"), constraintsLast); obfuscationOptionsPanel.add(tip(obfuscationDictionaryCheckBox, "obfuscationDictionaryTip"), constraints); obfuscationOptionsPanel.add(tip(obfuscationDictionaryTextField, "inputFileTip"), constraintsStretch); obfuscationOptionsPanel.add(tip(obfucationDictionaryBrowseButton, "selectObfuscationDictionaryFile"), constraintsLast); obfuscationOptionsPanel.add(tip(classObfuscationDictionaryCheckBox, "classObfuscationDictionaryTip"), constraints); obfuscationOptionsPanel.add(tip(classObfuscationDictionaryTextField, "inputFileTip"), constraintsStretch); obfuscationOptionsPanel.add(tip(classObfucationDictionaryBrowseButton, "selectObfuscationDictionaryFile"), constraintsLast); obfuscationOptionsPanel.add(tip(packageObfuscationDictionaryCheckBox, "packageObfuscationDictionaryTip"), constraints); obfuscationOptionsPanel.add(tip(packageObfuscationDictionaryTextField, "inputFileTip"), constraintsStretch); obfuscationOptionsPanel.add(tip(packageObfucationDictionaryBrowseButton, "selectObfuscationDictionaryFile"), constraintsLast); obfuscationOptionsPanel.add(tip(overloadAggressivelyCheckBox, "overloadAggressivelyTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(useUniqueClassMemberNamesCheckBox, "useUniqueClassMemberNamesTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(useMixedCaseClassNamesCheckBox, "useMixedCaseClassNamesTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(keepPackageNamesCheckBox, "keepPackageNamesTip"), constraints); obfuscationOptionsPanel.add(tip(keepPackageNamesTextField, "packageNamesTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(flattenPackageHierarchyCheckBox, "flattenPackageHierarchyTip"), constraints); obfuscationOptionsPanel.add(tip(flattenPackageHierarchyTextField, "packageTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(repackageClassesCheckBox, "repackageClassesTip"), constraints); obfuscationOptionsPanel.add(tip(repackageClassesTextField, "packageTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(keepAttributesCheckBox, "keepAttributesTip"), constraints); obfuscationOptionsPanel.add(tip(keepAttributesTextField, "attributesTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(keepParameterNamesCheckBox, "keepParameterNamesTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(newSourceFileAttributeCheckBox, "renameSourceFileAttributeTip"), constraints); obfuscationOptionsPanel.add(tip(newSourceFileAttributeTextField, "sourceFileAttributeTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(adaptClassStringsCheckBox, "adaptClassStringsTip"), constraints); obfuscationOptionsPanel.add(tip(adaptClassStringsTextField, "classNamesTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(adaptResourceFileNamesCheckBox, "adaptResourceFileNamesTip"), constraints); obfuscationOptionsPanel.add(tip(adaptResourceFileNamesTextField, "fileNameFilterTip"), constraintsLastStretch); obfuscationOptionsPanel.add(tip(adaptResourceFileContentsCheckBox, "adaptResourceFileContentsTip"), constraints); obfuscationOptionsPanel.add(tip(adaptResourceFileContentsTextField, "fileNameFilterTip"), constraintsLastStretch); JPanel obfuscationPanel = new JPanel(layout); obfuscationPanel.add(obfuscationOptionsPanel, panelConstraints); addClassSpecifications(extractClassSpecifications(boilerplateKeepNames), boilerplateKeepNamesCheckBoxes, boilerplateKeepNamesTextFields, obfuscationPanel); addBorder(additionalKeepNamesPanel, "keepNamesAdditional"); obfuscationPanel.add(tip(additionalKeepNamesPanel, "keepNamesAdditionalTip"), stretchPanelConstraints); // Create the boiler plate "no side effect methods" panels. boilerplateNoSideEffectMethodCheckBoxes = new JCheckBox[boilerplateNoSideEffectMethods.length]; JPanel optimizationOptionsPanel = new JPanel(layout); addBorder(optimizationOptionsPanel, "options"); JButton optimizationsButton = createOptimizationsButton(optimizationsTextField); optimizationOptionsPanel.add(tip(optimizeCheckBox, "optimizeTip"), constraintsLastStretch); optimizationOptionsPanel.add(tip(allowAccessModificationCheckBox, "allowAccessModificationTip"), constraintsLastStretch); optimizationOptionsPanel.add(tip(mergeInterfacesAggressivelyCheckBox, "mergeInterfacesAggressivelyTip"), constraintsLastStretch); optimizationOptionsPanel.add(tip(optimizationsLabel, "optimizationsTip"), constraints); optimizationOptionsPanel.add(tip(optimizationsTextField, "optimizationsFilterTip"), constraintsStretch); optimizationOptionsPanel.add(tip(optimizationsButton, "optimizationsSelectTip"), constraintsLast); optimizationOptionsPanel.add(tip(optimizationPassesLabel, "optimizationPassesTip"), constraints); optimizationOptionsPanel.add(tip(optimizationPassesSpinner, "optimizationPassesTip"), constraintsLast); JPanel optimizationPanel = new JPanel(layout); optimizationPanel.add(optimizationOptionsPanel, panelConstraints); addClassSpecifications(boilerplateNoSideEffectMethods, boilerplateNoSideEffectMethodCheckBoxes, null, optimizationPanel); addBorder(additionalNoSideEffectsPanel, "assumeNoSideEffectsAdditional"); optimizationPanel.add(tip(additionalNoSideEffectsPanel, "assumeNoSideEffectsAdditionalTip"), stretchPanelConstraints); // Create the options panel. JPanel preverificationOptionsPanel = new JPanel(layout); addBorder(preverificationOptionsPanel, "preverificationAndTargeting"); preverificationOptionsPanel.add(tip(preverifyCheckBox, "preverifyTip"), constraintsLastStretch); preverificationOptionsPanel.add(tip(microEditionCheckBox, "microEditionTip"), constraintsLastStretch); preverificationOptionsPanel.add(tip(androidCheckBox, "androidTip"), constraintsLastStretch); preverificationOptionsPanel.add(tip(targetCheckBox, "targetTip"), constraints); preverificationOptionsPanel.add(tip(targetComboBox, "targetTip"), constraintsLast); JButton printSeedsBrowseButton = createBrowseButton(printSeedsTextField, msg("selectSeedsFile")); JButton printConfigurationBrowseButton = createBrowseButton(printConfigurationTextField, msg( "selectConfigurationFile")); JButton dumpBrowseButton = createBrowseButton(dumpTextField, msg("selectDumpFile")); // Select the most recent target by default. targetComboBox.setSelectedIndex(targetComboBox.getItemCount() - 1); JPanel consistencyPanel = new JPanel(layout); addBorder(consistencyPanel, "consistencyAndCorrectness"); consistencyPanel.add(tip(verboseCheckBox, "verboseTip"), constraintsLastStretch); consistencyPanel.add(tip(noteCheckBox, "noteTip"), constraints); consistencyPanel.add(tip(noteTextField, "noteFilterTip"), constraintsLastStretch); consistencyPanel.add(tip(warnCheckBox, "warnTip"), constraints); consistencyPanel.add(tip(warnTextField, "warnFilterTip"), constraintsLastStretch); consistencyPanel.add(tip(ignoreWarningsCheckBox, "ignoreWarningsTip"), constraintsLastStretch); consistencyPanel.add(tip(skipNonPublicLibraryClassesCheckBox, "skipNonPublicLibraryClassesTip"), constraintsLastStretch); consistencyPanel.add(tip(skipNonPublicLibraryClassMembersCheckBox, "skipNonPublicLibraryClassMembersTip"), constraintsLastStretch); consistencyPanel.add(tip(keepDirectoriesCheckBox, "keepDirectoriesTip"), constraints); consistencyPanel.add(tip(keepDirectoriesTextField, "directoriesTip"), constraintsLastStretch); consistencyPanel.add(tip(forceProcessingCheckBox, "forceProcessingTip"), constraintsLastStretch); consistencyPanel.add(tip(printSeedsCheckBox, "printSeedsTip"), constraints); consistencyPanel.add(tip(printSeedsTextField, "outputFileTip"), constraintsStretch); consistencyPanel.add(tip(printSeedsBrowseButton, "selectSeedsFile"), constraintsLast); consistencyPanel.add(tip(printConfigurationCheckBox, "printConfigurationTip"), constraints); consistencyPanel.add(tip(printConfigurationTextField, "outputFileTip"), constraintsStretch); consistencyPanel.add(tip(printConfigurationBrowseButton, "selectConfigurationFile"), constraintsLast); consistencyPanel.add(tip(dumpCheckBox, "dumpTip"), constraints); consistencyPanel.add(tip(dumpTextField, "outputFileTip"), constraintsStretch); consistencyPanel.add(tip(dumpBrowseButton, "selectDumpFile"), constraintsLast); // Collect all components that are followed by text fields and make // sure they are equally sized. That way the text fields start at the // same horizontal position. setCommonPreferredSize(Arrays.asList(new JComponent[] { printMappingCheckBox, applyMappingCheckBox, flattenPackageHierarchyCheckBox, repackageClassesCheckBox, newSourceFileAttributeCheckBox, })); JPanel optionsPanel = new JPanel(layout); optionsPanel.add(preverificationOptionsPanel, panelConstraints); optionsPanel.add(consistencyPanel, panelConstraints); addBorder(whyAreYouKeepingPanel, "whyAreYouKeeping"); optionsPanel.add(tip(whyAreYouKeepingPanel, "whyAreYouKeepingTip"), stretchPanelConstraints); // Create the process panel. consoleTextArea.setOpaque(false); consoleTextArea.setEditable(false); consoleTextArea.setLineWrap(false); consoleTextArea.setWrapStyleWord(false); createLogAppender(consoleTextArea); JScrollPane consoleScrollPane = new JScrollPane(consoleTextArea); consoleScrollPane.setBorder(new EmptyBorder(1, 1, 1, 1)); addBorder(consoleScrollPane, "processingConsole"); JPanel processPanel = new JPanel(layout); processPanel.add(consoleScrollPane, stretchPanelConstraints); // Create the load, save, and process buttons. JButton loadButton = new JButton(msg("loadConfiguration")); loadButton.addActionListener(new MyLoadConfigurationActionListener()); JButton viewButton = new JButton(msg("viewConfiguration")); viewButton.addActionListener(new MyViewConfigurationActionListener()); JButton saveButton = new JButton(msg("saveConfiguration")); saveButton.addActionListener(new MySaveConfigurationActionListener()); JButton processButton = new JButton(msg("process")); processButton.addActionListener(new MyProcessActionListener()); // Create the ReTrace panel. JPanel reTraceSettingsPanel = new JPanel(layout); addBorder(reTraceSettingsPanel, "reTraceSettings"); JButton reTraceMappingBrowseButton = createBrowseButton(reTraceMappingTextField, msg("selectApplyMappingFile")); JLabel reTraceMappingLabel = new JLabel(msg("mappingFile")); reTraceMappingLabel.setForeground(reTraceVerboseCheckBox.getForeground()); reTraceSettingsPanel.add(tip(reTraceVerboseCheckBox, "verboseTip"), constraintsLastStretch); reTraceSettingsPanel.add(tip(reTraceMappingLabel, "mappingFileTip"), constraints); reTraceSettingsPanel.add(tip(reTraceMappingTextField, "inputFileTip"), constraintsStretch); reTraceSettingsPanel.add(tip(reTraceMappingBrowseButton, "selectApplyMappingFile"), constraintsLast); stackTraceTextArea.setOpaque(true); stackTraceTextArea.setEditable(true); stackTraceTextArea.setLineWrap(false); stackTraceTextArea.setWrapStyleWord(true); JScrollPane stackTraceScrollPane = new JScrollPane(stackTraceTextArea); addBorder(stackTraceScrollPane, "obfuscatedStackTrace"); reTraceTextArea.setOpaque(false); reTraceTextArea.setEditable(false); reTraceTextArea.setLineWrap(true); reTraceTextArea.setWrapStyleWord(true); JScrollPane reTraceScrollPane = new JScrollPane(reTraceTextArea); reTraceScrollPane.setBorder(new EmptyBorder(1, 1, 1, 1)); addBorder(reTraceScrollPane, "deobfuscatedStackTrace"); JPanel reTracePanel = new JPanel(layout); reTracePanel.add(reTraceSettingsPanel, panelConstraints); reTracePanel.add(tip(stackTraceScrollPane, "obfuscatedStackTraceTip"), panelConstraints); reTracePanel.add(reTraceScrollPane, stretchPanelConstraints); // Create the load button. JButton loadStackTraceButton = new JButton(msg("loadStackTrace")); loadStackTraceButton.addActionListener(new MyLoadStackTraceActionListener()); JButton reTraceButton = new JButton(msg("reTrace")); reTraceButton.addActionListener(new MyReTraceActionListener()); // Make the shrinking panel so far scrollable. JPanel scrollableShrinkingPanel = shrinkingPanel; shrinkingPanel = new JPanel(layout); shrinkingPanel.add(new JScrollPane(scrollableShrinkingPanel), containerConstraints); // Make the obfuscation panel so far scrollable. JPanel scrollableObfuscationPanel = obfuscationPanel; obfuscationPanel = new JPanel(layout); obfuscationPanel.add(new JScrollPane(scrollableObfuscationPanel), containerConstraints); // Make the optimization panel so far scrollable. JPanel scrollableOptimizationPanel = optimizationPanel; optimizationPanel = new JPanel(layout); optimizationPanel.add(new JScrollPane(scrollableOptimizationPanel), containerConstraints); // Create the main tabbed pane. TabbedPane tabs = new TabbedPane(); tabs.add(msg("proGuardTab"), proGuardPanel); tabs.add(msg("inputOutputTab"), inputOutputPanel); tabs.add(msg("shrinkingTab"), shrinkingPanel); tabs.add(msg("obfuscationTab"), obfuscationPanel); tabs.add(msg("optimizationTab"), optimizationPanel); tabs.add(msg("informationTab"), optionsPanel); tabs.add(msg("processTab"), processPanel); tabs.add(msg("reTraceTab"), reTracePanel); tabs.addImage(Toolkit.getDefaultToolkit().getImage( this.getClass().getResource(TITLE_IMAGE_FILE))); // Add the bottom buttons to each panel. proGuardPanel .add(Box.createGlue(), glueConstraints); proGuardPanel .add(tip(loadButton, "loadConfigurationTip"), bottomButtonConstraints); proGuardPanel .add(createNextButton(tabs), lastBottomButtonConstraints); inputOutputPanel .add(Box.createGlue(), glueConstraints); inputOutputPanel .add(createPreviousButton(tabs), bottomButtonConstraints); inputOutputPanel .add(createNextButton(tabs), lastBottomButtonConstraints); shrinkingPanel .add(Box.createGlue(), glueConstraints); shrinkingPanel .add(createPreviousButton(tabs), bottomButtonConstraints); shrinkingPanel .add(createNextButton(tabs), lastBottomButtonConstraints); obfuscationPanel .add(Box.createGlue(), glueConstraints); obfuscationPanel .add(createPreviousButton(tabs), bottomButtonConstraints); obfuscationPanel .add(createNextButton(tabs), lastBottomButtonConstraints); optimizationPanel.add(Box.createGlue(), glueConstraints); optimizationPanel.add(createPreviousButton(tabs), bottomButtonConstraints); optimizationPanel.add(createNextButton(tabs), lastBottomButtonConstraints); optionsPanel .add(Box.createGlue(), glueConstraints); optionsPanel .add(createPreviousButton(tabs), bottomButtonConstraints); optionsPanel .add(createNextButton(tabs), lastBottomButtonConstraints); processPanel .add(Box.createGlue(), glueConstraints); processPanel .add(createPreviousButton(tabs), bottomButtonConstraints); processPanel .add(tip(viewButton, "viewConfigurationTip"), bottomButtonConstraints); processPanel .add(tip(saveButton, "saveConfigurationTip"), bottomButtonConstraints); processPanel .add(tip(processButton, "processTip"), lastBottomButtonConstraints); reTracePanel .add(Box.createGlue(), glueConstraints); reTracePanel .add(tip(loadStackTraceButton, "loadStackTraceTip"), bottomButtonConstraints); reTracePanel .add(tip(reTraceButton, "reTraceTip"), lastBottomButtonConstraints); // Add the main tabs to the frame. getContentPane().add(tabs); // Pack the entire GUI before setting some default values. pack(); // Initialize the GUI settings to reasonable defaults. loadConfiguration(this.getClass().getResource(DEFAULT_CONFIGURATION)); } private void createLogAppender(JTextArea consoleTextArea) { LoggerContext ctx = (LoggerContext) LogManager.getContext(false); org.apache.logging.log4j.core.config.Configuration config = ctx.getConfiguration(); OutputStreamAppender writerAppender = OutputStreamAppender .newBuilder() .setName("writeLogger") .setTarget(new PrintStream(new TextAreaOutputStream(consoleTextArea), true)) .setLayout(PatternLayout.newBuilder().withPattern("%msg%n").build()) .build(); writerAppender.start(); config.getRootLogger().removeAppender("Console"); config.addAppender(writerAppender); LoggerConfig loggerConfig = config.getLoggerConfig("TextAreaLogger") ; loggerConfig.addAppender(writerAppender, null, null); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { LoggerContext.getContext(false).stop(); e.getWindow().dispose(); } }); } public void startSplash() { splashPanel.start(); } public void skipSplash() { splashPanel.stop(); } /** * Loads the boilerplate keep class options from the boilerplate file * into the boilerplate array. */ private void loadBoilerplateConfiguration() { try { // Parse the boilerplate configuration file. try (ConfigurationParser parser = new ConfigurationParser( this.getClass().getResource(BOILERPLATE_CONFIGURATION), System.getProperties())) { Configuration configuration = new Configuration(); parser.parse(configuration); // We're interested in the keep options. boilerplateKeep = extractKeepSpecifications(configuration.keep, false, false); // We're interested in the keep options. boilerplateKeepNames = extractKeepSpecifications(configuration.keep, true, false); // We're interested in the side effects options. boilerplateNoSideEffectMethods = new ClassSpecification[configuration.assumeNoSideEffects.size()]; configuration.assumeNoSideEffects.toArray(boilerplateNoSideEffectMethods); } } catch (Exception ex) { ex.printStackTrace(); } } /** * Returns an array containing the ClassSpecifications instances with * matching flags. */ private KeepClassSpecification[] extractKeepSpecifications(List keepSpecifications, boolean allowShrinking, boolean allowObfuscation) { List matches = new ArrayList(); for (int index = 0; index < keepSpecifications.size(); index++) { KeepClassSpecification keepClassSpecification = (KeepClassSpecification)keepSpecifications.get(index); if (keepClassSpecification.allowShrinking == allowShrinking && keepClassSpecification.allowObfuscation == allowObfuscation) { matches.add(keepClassSpecification); } } KeepClassSpecification[] matchingKeepClassSpecifications = new KeepClassSpecification[matches.size()]; matches.toArray(matchingKeepClassSpecifications); return matchingKeepClassSpecifications; } /** * Returns an array containing the ClassSpecification instances of the * given array of KeepClassSpecification instances. */ private ClassSpecification[] extractClassSpecifications(KeepClassSpecification[] keepClassSpecifications) { ClassSpecification[] classSpecifications = new ClassSpecification[keepClassSpecifications.length]; for (int index = 0; index < classSpecifications.length; index++) { classSpecifications[index] = keepClassSpecifications[index]; } return classSpecifications; } /** * Creates a panel with the given boiler plate class specifications. */ private void addClassSpecifications(ClassSpecification[] boilerplateClassSpecifications, JCheckBox[] boilerplateCheckBoxes, JTextField[] boilerplateTextFields, JPanel classSpecificationsPanel) { // Create some constraints that can be reused. GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.insets = new Insets(0, 4, 0, 4); GridBagConstraints constraintsLastStretch = new GridBagConstraints(); constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER; constraintsLastStretch.fill = GridBagConstraints.HORIZONTAL; constraintsLastStretch.weightx = 1.0; constraintsLastStretch.anchor = GridBagConstraints.WEST; constraintsLastStretch.insets = constraints.insets; GridBagConstraints panelConstraints = new GridBagConstraints(); panelConstraints.gridwidth = GridBagConstraints.REMAINDER; panelConstraints.fill = GridBagConstraints.HORIZONTAL; panelConstraints.weightx = 1.0; panelConstraints.anchor = GridBagConstraints.NORTHWEST; panelConstraints.insets = constraints.insets; GridBagConstraints containerConstraints = new GridBagConstraints(); containerConstraints.gridwidth = GridBagConstraints.REMAINDER; containerConstraints.fill = GridBagConstraints.BOTH; containerConstraints.weightx = 1.0; containerConstraints.weighty = 1.0; containerConstraints.anchor = GridBagConstraints.NORTHWEST; GridBagLayout layout = new GridBagLayout(); JPanel panel = new JPanel(layout); String lastPanelName = null; JPanel subpanel = null; for (int index = 0; index < boilerplateClassSpecifications.length; index++) { // The panel structure is derived from the comments. String comments = boilerplateClassSpecifications[index].comments; // Do we have a comment, for a new set of options? if (comments != null) { // Create a new line in the panel. int dashIndex = comments.indexOf('-'); int periodIndex = comments.indexOf('.', dashIndex); String panelName = comments.substring(0, dashIndex).trim(); String optionName = comments.substring(dashIndex + 1, periodIndex).replace('_', '.').trim(); String toolTip = comments.substring(periodIndex + 1); if (!panelName.equals(lastPanelName)) { // Create a new subpanel and add it. subpanel = new JPanel(layout); if (panelName.endsWith(ELLIPSIS_DOTS)) { subpanel.setVisible(false); } // Make the contents foldable. final JPanel foldableSubpanel = new JPanel(layout); final TitledBorder titledBorder = BorderFactory.createTitledBorder(BORDER, panelName); foldableSubpanel.setBorder(titledBorder); foldableSubpanel.add(subpanel, containerConstraints); // Fold and unfold the contents when the foldable panel // is clicked. final JPanel finalSubpanel = subpanel; foldableSubpanel.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent mouseEvent) { // Toggle the visibility and the title. String title = titledBorder.getTitle(); if (finalSubpanel.isVisible()) { finalSubpanel.setVisible(false); titledBorder.setTitle(title + ELLIPSIS_DOTS); } else { finalSubpanel.setVisible(true); titledBorder.setTitle(title.substring(0, title.length()-3)); } } }); classSpecificationsPanel.add(foldableSubpanel, panelConstraints); lastPanelName = panelName; } // Should we add a text field for the class names? boolean addTextField = boilerplateTextFields != null && (index+1 == boilerplateClassSpecifications.length || boilerplateClassSpecifications[index+1].comments != null) && boilerplateClassSpecifications[index].className == null; ; // Add the check box to the subpanel. JCheckBox boilerplateCheckBox = new JCheckBox(optionName); boilerplateCheckBox.setToolTipText(toolTip); subpanel.add(boilerplateCheckBox, addTextField ? constraints : constraintsLastStretch); boilerplateCheckBoxes[index] = boilerplateCheckBox; // Add the text field to the subpanel, if relevant. if (addTextField) { JTextField boilerplateTextField = new JTextField(40); subpanel.add(tip(boilerplateTextField, "classNamesTip"), constraintsLastStretch); boilerplateTextFields[index] = boilerplateTextField; } } else { // Reuse the previous line from the panel, notably the check // box. boilerplateCheckBoxes[index] = boilerplateCheckBoxes[index-1]; } } } /** * Adds a standard border with the title that corresponds to the given key * in the GUI resources. */ private void addBorder(JComponent component, String titleKey) { Border oldBorder = component.getBorder(); Border newBorder = BorderFactory.createTitledBorder(BORDER, msg(titleKey)); component.setBorder(oldBorder == null ? newBorder : new CompoundBorder(newBorder, oldBorder)); } /** * Creates a Previous button for the given tabbed pane. */ private JButton createPreviousButton(final TabbedPane tabbedPane) { JButton browseButton = new JButton(msg("previous")); browseButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { tabbedPane.previous(); } }); return browseButton; } /** * Creates a Next button for the given tabbed pane. */ private JButton createNextButton(final TabbedPane tabbedPane) { JButton browseButton = new JButton(msg("next")); browseButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { tabbedPane.next(); } }); return browseButton; } /** * Creates a browse button that opens a file browser for the given text field. */ private JButton createBrowseButton(final JTextField textField, final String title) { JButton browseButton = new JButton(msg("browse")); browseButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fileChooser.setCurrentDirectory(new File(PREFS.get("LAST_MAPPING_FILE", new File(".").getAbsolutePath()))); // Update the file chooser. fileChooser.setDialogTitle(title); fileChooser.setSelectedFile(new File(textField.getText())); int returnVal = fileChooser.showDialog(ProGuardGUI.this, msg("ok")); if (returnVal == JFileChooser.APPROVE_OPTION) { // Update the text field. textField.setText(fileChooser.getSelectedFile().getPath()); // Save the last opened folder for the next usage of this panel (it's kept even on app restarts) PREFS.put("LAST_MAPPING_FILE", fileChooser.getSelectedFile().getAbsolutePath()); } } }); return browseButton; } protected JButton createOptimizationsButton(final JTextField textField) { final OptimizationsDialog optimizationsDialog = new OptimizationsDialog(ProGuardGUI.this); JButton optimizationsButton = new JButton(msg("select")); optimizationsButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Update the dialog. optimizationsDialog.setFilter(textField.getText()); int returnValue = optimizationsDialog.showDialog(); if (returnValue == OptimizationsDialog.APPROVE_OPTION) { // Update the text field. textField.setText(optimizationsDialog.getFilter()); } } }); return optimizationsButton; } /** * Sets the preferred sizes of the given components to the maximum of their * current preferred sizes. */ private void setCommonPreferredSize(List components) { // Find the maximum preferred size. Dimension maximumSize = null; for (int index = 0; index < components.size(); index++) { JComponent component = (JComponent)components.get(index); Dimension size = component.getPreferredSize(); if (maximumSize == null || size.getWidth() > maximumSize.getWidth()) { maximumSize = size; } } // Set the size that we found as the preferred size for all components. for (int index = 0; index < components.size(); index++) { JComponent component = (JComponent)components.get(index); component.setPreferredSize(maximumSize); } } /** * Updates to GUI settings to reflect the given ProGuard configuration. */ private void setProGuardConfiguration(Configuration configuration) { // Set up the input and output jars and directories. programPanel.setClassPath(configuration.programJars); libraryPanel.setClassPath(configuration.libraryJars); // Set up the boilerplate keep options. for (int index = 0; index < boilerplateKeep.length; index++) { String classNames = findMatchingKeepSpecifications(boilerplateKeep[index], boilerplateKeepTextFields[index] == null, configuration.keep); boilerplateKeepCheckBoxes[index].setSelected(classNames != null); if (boilerplateKeepTextFields[index] != null) { boilerplateKeepTextFields[index].setText(classNames == null ? "*" : classNames); } } // Set up the boilerplate keep names options. for (int index = 0; index < boilerplateKeepNames.length; index++) { String classNames = findMatchingKeepSpecifications(boilerplateKeepNames[index], boilerplateKeepNamesTextFields[index] == null, configuration.keep); boilerplateKeepNamesCheckBoxes[index].setSelected(classNames != null); if (boilerplateKeepNamesTextFields[index] != null) { boilerplateKeepNamesTextFields[index].setText(classNames == null ? "*" : classNames); } } // Set up the additional keep options. Note that the matched boilerplate // options have been removed from the list. additionalKeepPanel.setClassSpecifications(filteredKeepSpecifications(configuration.keep, false)); // Set up the additional keep options. Note that the matched boilerplate // options have been removed from the list. additionalKeepNamesPanel.setClassSpecifications(filteredKeepSpecifications(configuration.keep, true)); // Set up the boilerplate "no side effect methods" options. for (int index = 0; index < boilerplateNoSideEffectMethods.length; index++) { boolean found = findClassSpecification(boilerplateNoSideEffectMethods[index], configuration.assumeNoSideEffects); boilerplateNoSideEffectMethodCheckBoxes[index].setSelected(found); } // Set up the additional keep options. Note that the matched boilerplate // options have been removed from the list. additionalNoSideEffectsPanel.setClassSpecifications(configuration.assumeNoSideEffects); // Set up the "why are you keeping" options. whyAreYouKeepingPanel.setClassSpecifications(configuration.whyAreYouKeeping); // Set up the other options. shrinkCheckBox .setSelected(configuration.shrink); printUsageCheckBox .setSelected(configuration.printUsage != null); optimizeCheckBox .setSelected(configuration.optimize); allowAccessModificationCheckBox .setSelected(configuration.allowAccessModification); mergeInterfacesAggressivelyCheckBox .setSelected(configuration.mergeInterfacesAggressively); optimizationPassesSpinner.getModel() .setValue(configuration.optimizationPasses); obfuscateCheckBox .setSelected(configuration.obfuscate); printMappingCheckBox .setSelected(configuration.printMapping != null); applyMappingCheckBox .setSelected(configuration.applyMapping != null); obfuscationDictionaryCheckBox .setSelected(configuration.obfuscationDictionary != null); classObfuscationDictionaryCheckBox .setSelected(configuration.classObfuscationDictionary != null); packageObfuscationDictionaryCheckBox .setSelected(configuration.packageObfuscationDictionary != null); overloadAggressivelyCheckBox .setSelected(configuration.overloadAggressively); useUniqueClassMemberNamesCheckBox .setSelected(configuration.useUniqueClassMemberNames); useMixedCaseClassNamesCheckBox .setSelected(configuration.useMixedCaseClassNames); keepPackageNamesCheckBox .setSelected(configuration.keepPackageNames != null); flattenPackageHierarchyCheckBox .setSelected(configuration.flattenPackageHierarchy != null); repackageClassesCheckBox .setSelected(configuration.repackageClasses != null); keepAttributesCheckBox .setSelected(configuration.keepAttributes != null); keepParameterNamesCheckBox .setSelected(configuration.keepParameterNames); newSourceFileAttributeCheckBox .setSelected(configuration.newSourceFileAttribute != null); adaptClassStringsCheckBox .setSelected(configuration.adaptClassStrings != null); adaptResourceFileNamesCheckBox .setSelected(configuration.adaptResourceFileNames != null); adaptResourceFileContentsCheckBox .setSelected(configuration.adaptResourceFileContents != null); preverifyCheckBox .setSelected(configuration.preverify); microEditionCheckBox .setSelected(configuration.microEdition); androidCheckBox .setSelected(configuration.android); targetCheckBox .setSelected(configuration.targetClassVersion != 0); verboseCheckBox .setSelected(configuration.verbose); noteCheckBox .setSelected(configuration.note == null || !configuration.note.isEmpty()); warnCheckBox .setSelected(configuration.warn == null || !configuration.warn.isEmpty()); ignoreWarningsCheckBox .setSelected(configuration.ignoreWarnings); skipNonPublicLibraryClassesCheckBox .setSelected(configuration.skipNonPublicLibraryClasses); skipNonPublicLibraryClassMembersCheckBox.setSelected(configuration.skipNonPublicLibraryClassMembers); keepDirectoriesCheckBox .setSelected(configuration.keepDirectories != null); forceProcessingCheckBox .setSelected(configuration.lastModified == Long.MAX_VALUE); printSeedsCheckBox .setSelected(configuration.printSeeds != null); printConfigurationCheckBox .setSelected(configuration.printConfiguration != null); dumpCheckBox .setSelected(configuration.dump != null); printUsageTextField .setText(fileName(configuration.printUsage)); optimizationsTextField .setText(configuration.optimizations == null ? OPTIMIZATIONS_DEFAULT : ListUtil.commaSeparatedString(configuration.optimizations, true)); printMappingTextField .setText(fileName(configuration.printMapping)); applyMappingTextField .setText(fileName(configuration.applyMapping)); obfuscationDictionaryTextField .setText(fileName(configuration.obfuscationDictionary)); classObfuscationDictionaryTextField .setText(fileName(configuration.classObfuscationDictionary)); packageObfuscationDictionaryTextField .setText(fileName(configuration.packageObfuscationDictionary)); keepPackageNamesTextField .setText(configuration.keepPackageNames == null ? "" : ClassUtil.externalClassName(ListUtil.commaSeparatedString(configuration.keepPackageNames, true))); flattenPackageHierarchyTextField .setText(configuration.flattenPackageHierarchy); repackageClassesTextField .setText(configuration.repackageClasses); keepAttributesTextField .setText(configuration.keepAttributes == null ? KEEP_ATTRIBUTE_DEFAULT : ListUtil.commaSeparatedString(configuration.keepAttributes, true)); newSourceFileAttributeTextField .setText(configuration.newSourceFileAttribute == null ? SOURCE_FILE_ATTRIBUTE_DEFAULT : configuration.newSourceFileAttribute); adaptClassStringsTextField .setText(configuration.adaptClassStrings == null ? "" : ClassUtil.externalClassName(ListUtil.commaSeparatedString(configuration.adaptClassStrings, true))); adaptResourceFileNamesTextField .setText(configuration.adaptResourceFileNames == null ? ADAPT_RESOURCE_FILE_NAMES_DEFAULT : ListUtil.commaSeparatedString(configuration.adaptResourceFileNames, true)); adaptResourceFileContentsTextField .setText(configuration.adaptResourceFileContents == null ? ADAPT_RESOURCE_FILE_CONTENTS_DEFAULT : ListUtil.commaSeparatedString(configuration.adaptResourceFileContents, true)); noteTextField .setText(ListUtil.commaSeparatedString(configuration.note, true)); warnTextField .setText(ListUtil.commaSeparatedString(configuration.warn, true)); keepDirectoriesTextField .setText(ListUtil.commaSeparatedString(configuration.keepDirectories, true)); printSeedsTextField .setText(fileName(configuration.printSeeds)); printConfigurationTextField .setText(fileName(configuration.printConfiguration)); dumpTextField .setText(fileName(configuration.dump)); if (configuration.targetClassVersion != 0) { targetComboBox.setSelectedItem(ClassUtil.externalClassVersion(configuration.targetClassVersion)); } else { targetComboBox.setSelectedIndex(targetComboBox.getItemCount() - 1); } if (configuration.printMapping != null) { reTraceMappingTextField.setText(fileName(configuration.printMapping)); } else { reTraceMappingTextField.setText(PREFS.get("LAST_MAPPING_FILE", "")); } } /** * Returns the ProGuard configuration that reflects the current GUI settings. */ private Configuration getProGuardConfiguration() { Configuration configuration = new Configuration(); // Get the input and output jars and directories. configuration.programJars = programPanel.getClassPath(); configuration.libraryJars = libraryPanel.getClassPath(); List keep = new ArrayList(); // Collect the additional keep options. List additionalKeep = additionalKeepPanel.getClassSpecifications(); if (additionalKeep != null) { keep.addAll(additionalKeep); } // Collect the additional keep names options. List additionalKeepNames = additionalKeepNamesPanel.getClassSpecifications(); if (additionalKeepNames != null) { keep.addAll(additionalKeepNames); } // Collect the boilerplate keep options. for (int index = 0; index < boilerplateKeep.length; index++) { if (boilerplateKeepCheckBoxes[index].isSelected()) { keep.add(classSpecification(boilerplateKeep[index], boilerplateKeepTextFields[index] == null ? null : boilerplateKeepTextFields[index].getText())); } } // Collect the boilerplate keep names options. for (int index = 0; index < boilerplateKeepNames.length; index++) { if (boilerplateKeepNamesCheckBoxes[index].isSelected()) { keep.add(classSpecification(boilerplateKeepNames[index], boilerplateKeepNamesTextFields[index] == null ? null : boilerplateKeepNamesTextFields[index].getText())); } } // Put the list of keep specifications in the configuration. if (keep.size() > 0) { configuration.keep = keep; } // Collect the boilerplate "no side effect methods" options. List noSideEffectMethods = new ArrayList(); for (int index = 0; index < boilerplateNoSideEffectMethods.length; index++) { if (boilerplateNoSideEffectMethodCheckBoxes[index].isSelected()) { noSideEffectMethods.add(boilerplateNoSideEffectMethods[index]); } } // Collect the additional "no side effect methods" options. List additionalNoSideEffectOptions = additionalNoSideEffectsPanel.getClassSpecifications(); if (additionalNoSideEffectOptions != null) { noSideEffectMethods.addAll(additionalNoSideEffectOptions); } // Put the list of "no side effect methods" options in the configuration. if (noSideEffectMethods.size() > 0) { configuration.assumeNoSideEffects = noSideEffectMethods; } // Collect the "why are you keeping" options. configuration.whyAreYouKeeping = whyAreYouKeepingPanel.getClassSpecifications(); // Get the other options. configuration.shrink = shrinkCheckBox .isSelected(); configuration.printUsage = printUsageCheckBox .isSelected() ? new File(printUsageTextField .getText()) : null; configuration.optimize = optimizeCheckBox .isSelected(); configuration.allowAccessModification = allowAccessModificationCheckBox .isSelected(); configuration.mergeInterfacesAggressively = mergeInterfacesAggressivelyCheckBox .isSelected(); configuration.optimizations = optimizationsTextField.getText().length() > 1 ? ListUtil.commaSeparatedList(optimizationsTextField .getText()) : null; configuration.optimizationPasses = ((SpinnerNumberModel)optimizationPassesSpinner.getModel()).getNumber().intValue(); configuration.obfuscate = obfuscateCheckBox .isSelected(); configuration.printMapping = printMappingCheckBox .isSelected() ? new File(printMappingTextField .getText()) : null; configuration.applyMapping = applyMappingCheckBox .isSelected() ? new File(applyMappingTextField .getText()) : null; configuration.obfuscationDictionary = obfuscationDictionaryCheckBox .isSelected() ? url(obfuscationDictionaryTextField .getText()) : null; configuration.classObfuscationDictionary = classObfuscationDictionaryCheckBox .isSelected() ? url(classObfuscationDictionaryTextField .getText()) : null; configuration.packageObfuscationDictionary = packageObfuscationDictionaryCheckBox .isSelected() ? url(packageObfuscationDictionaryTextField .getText()) : null; configuration.overloadAggressively = overloadAggressivelyCheckBox .isSelected(); configuration.useUniqueClassMemberNames = useUniqueClassMemberNamesCheckBox .isSelected(); configuration.useMixedCaseClassNames = useMixedCaseClassNamesCheckBox .isSelected(); configuration.keepPackageNames = keepPackageNamesCheckBox .isSelected() ? keepPackageNamesTextField.getText().length() > 0 ? ListUtil.commaSeparatedList(ClassUtil.internalClassName(keepPackageNamesTextField.getText())) : new ArrayList() : null; configuration.flattenPackageHierarchy = flattenPackageHierarchyCheckBox .isSelected() ? ClassUtil.internalClassName(flattenPackageHierarchyTextField .getText()) : null; configuration.repackageClasses = repackageClassesCheckBox .isSelected() ? ClassUtil.internalClassName(repackageClassesTextField .getText()) : null; configuration.keepAttributes = keepAttributesCheckBox .isSelected() ? ListUtil.commaSeparatedList(keepAttributesTextField .getText()) : null; configuration.keepParameterNames = keepParameterNamesCheckBox .isSelected(); configuration.newSourceFileAttribute = newSourceFileAttributeCheckBox .isSelected() ? newSourceFileAttributeTextField .getText() : null; configuration.adaptClassStrings = adaptClassStringsCheckBox .isSelected() ? adaptClassStringsTextField.getText().length() > 0 ? ListUtil.commaSeparatedList(ClassUtil.internalClassName(adaptClassStringsTextField.getText())) : new ArrayList() : null; configuration.adaptResourceFileNames = adaptResourceFileNamesCheckBox .isSelected() ? ListUtil.commaSeparatedList(adaptResourceFileNamesTextField .getText()) : null; configuration.adaptResourceFileContents = adaptResourceFileContentsCheckBox .isSelected() ? ListUtil.commaSeparatedList(adaptResourceFileContentsTextField .getText()) : null; configuration.preverify = preverifyCheckBox .isSelected(); configuration.microEdition = microEditionCheckBox .isSelected(); configuration.targetClassVersion = targetCheckBox .isSelected() ? ClassUtil.internalClassVersion(targetComboBox.getSelectedItem().toString()) : 0; configuration.verbose = verboseCheckBox .isSelected(); configuration.note = noteCheckBox .isSelected() ? noteTextField.getText().length() > 0 ? ListUtil.commaSeparatedList(ClassUtil.internalClassName(noteTextField.getText())) : null : new ArrayList(); configuration.warn = warnCheckBox .isSelected() ? warnTextField.getText().length() > 0 ? ListUtil.commaSeparatedList(ClassUtil.internalClassName(warnTextField.getText())) : null : new ArrayList(); configuration.ignoreWarnings = ignoreWarningsCheckBox .isSelected(); configuration.skipNonPublicLibraryClasses = skipNonPublicLibraryClassesCheckBox .isSelected(); configuration.skipNonPublicLibraryClassMembers = skipNonPublicLibraryClassMembersCheckBox.isSelected(); configuration.keepDirectories = keepDirectoriesCheckBox .isSelected() ? keepDirectoriesTextField.getText().length() > 0 ? ListUtil.commaSeparatedList(ClassUtil.internalClassName(keepDirectoriesTextField.getText())) : new ArrayList() : null; configuration.lastModified = forceProcessingCheckBox .isSelected() ? Long.MAX_VALUE : System.currentTimeMillis(); configuration.printSeeds = printSeedsCheckBox .isSelected() ? new File(printSeedsTextField .getText()) : null; configuration.printConfiguration = printConfigurationCheckBox .isSelected() ? new File(printConfigurationTextField .getText()) : null; configuration.dump = dumpCheckBox .isSelected() ? new File(dumpTextField .getText()) : null; return configuration; } /** * Looks in the given list for a class specification that is identical to * the given template. Returns true if it is found, and removes the matching * class specification as a side effect. */ private boolean findClassSpecification(ClassSpecification classSpecificationTemplate, List classSpecifications) { if (classSpecifications == null) { return false; } for (int index = 0; index < classSpecifications.size(); index++) { if (classSpecificationTemplate.equals(classSpecifications.get(index))) { // Remove the matching option as a side effect. classSpecifications.remove(index); return true; } } return false; } /** * Returns the subset of the given list of keep specifications, with * matching shrinking flag. */ private List filteredKeepSpecifications(List keepSpecifications, boolean allowShrinking) { List filteredKeepSpecifications = new ArrayList(); for (int index = 0; index < keepSpecifications.size(); index++) { KeepClassSpecification keepClassSpecification = (KeepClassSpecification)keepSpecifications.get(index); if (keepClassSpecification.allowShrinking == allowShrinking) { filteredKeepSpecifications.add(keepClassSpecification); } } return filteredKeepSpecifications; } /** * Looks in the given list for keep specifications that match the given * template. Returns a comma-separated string of class names from * matching keep specifications, and removes the matching keep * specifications as a side effect. */ private String findMatchingKeepSpecifications(KeepClassSpecification keepClassSpecificationTemplate, boolean matchName, List keepSpecifications) { if (keepSpecifications == null) { return null; } StringBuffer buffer = null; for (int index = 0; index < keepSpecifications.size(); index++) { KeepClassSpecification listedKeepClassSpecification = (KeepClassSpecification)keepSpecifications.get(index); // Should we match the original template class name? String className = listedKeepClassSpecification.className; if (!matchName) { keepClassSpecificationTemplate.className = className; } if (keepClassSpecificationTemplate.equals(listedKeepClassSpecification)) { if (buffer == null) { buffer = new StringBuffer(); } else { buffer.append(','); } buffer.append(className == null ? "*" : ClassUtil.externalClassName(className)); // Remove the matching option as a side effect. keepSpecifications.remove(index--); } } return buffer == null ? null : buffer.toString(); } /** * Returns a class specification or keep specification, based on the given * template and the class name to be filled in. */ private ClassSpecification classSpecification(ClassSpecification classSpecificationTemplate, String className) { // Create a copy of the template. ClassSpecification classSpecification = (ClassSpecification)classSpecificationTemplate.clone(); // Set the class name in the copy. if (className != null) { classSpecification.className = className.equals("") || className.equals("*") ? null : ClassUtil.internalClassName(className); } // Return the modified copy. return classSpecification; } // Methods and internal classes related to actions. /** * Loads the given ProGuard configuration into the GUI. */ private void loadConfiguration(File file) { // Set the default directory and file in the file choosers. configurationChooser.setSelectedFile(file.getAbsoluteFile()); fileChooser.setCurrentDirectory(file.getAbsoluteFile().getParentFile()); try { // Parse the configuration file. try (ConfigurationParser parser = new ConfigurationParser(file, System.getProperties())) { Configuration configuration = new Configuration(); parser.parse(configuration); // Let the GUI reflect the configuration. setProGuardConfiguration(configuration); } catch (ParseException ex) { JOptionPane.showMessageDialog(getContentPane(), msg("cantParseConfigurationFile", file.getPath()), msg("warning"), JOptionPane.ERROR_MESSAGE); } } catch (IOException ex) { JOptionPane.showMessageDialog(getContentPane(), msg("cantOpenConfigurationFile", file.getPath()), msg("warning"), JOptionPane.ERROR_MESSAGE); } } /** * Loads the given ProGuard configuration into the GUI. */ private void loadConfiguration(URL url) { try { // Parse the configuration file. try (ConfigurationParser parser = new ConfigurationParser(url, System.getProperties())) { Configuration configuration = new Configuration(); parser.parse(configuration); configuration.libraryJars = new ClassPath(); if (JavaUtil.currentJavaVersion() > 8) { configuration.libraryJars.add( new ClassPathEntry(JavaUtil.getJmodBase(), false)); } else { configuration.libraryJars.add( new ClassPathEntry(JavaUtil.getRtJar(), false)); } // Let the GUI reflect the configuration. setProGuardConfiguration(configuration); } catch (ParseException ex) { JOptionPane.showMessageDialog(getContentPane(), msg("cantParseConfigurationFile", url), msg("warning"), JOptionPane.ERROR_MESSAGE); } } catch (IOException ex) { JOptionPane.showMessageDialog(getContentPane(), msg("cantOpenConfigurationFile", url), msg("warning"), JOptionPane.ERROR_MESSAGE); } } /** * Saves the current ProGuard configuration to the given file. */ private void saveConfiguration(File file) { try { // Save the configuration file. Configuration configuration = getProGuardConfiguration(); try (ConfigurationWriter writer = new ConfigurationWriter(file)) { writer.write(configuration); } } catch (Exception ex) { JOptionPane.showMessageDialog(getContentPane(), msg("cantSaveConfigurationFile", file.getPath()), msg("warning"), JOptionPane.ERROR_MESSAGE); } } /** * Loads the given stack trace into the GUI. */ private void loadStackTrace(File file) { try { StringBuffer buffer = new StringBuffer(1024); Reader reader = new BufferedReader( new InputStreamReader( new FileInputStream(file), "UTF-8")); try { while (true) { int c = reader.read(); if (c < 0) { break; } buffer.append(c); } } finally { reader.close(); } // Put the stack trace in the text area. stackTraceTextArea.setText(buffer.toString()); } catch (IOException ex) { JOptionPane.showMessageDialog(getContentPane(), msg("cantOpenStackTraceFile", fileName(file)), msg("warning"), JOptionPane.ERROR_MESSAGE); } } /** * This ActionListener loads a ProGuard configuration file and initializes * the GUI accordingly. */ private class MyLoadConfigurationActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { configurationChooser.setDialogTitle(msg("selectConfigurationFile")); int returnValue = configurationChooser.showOpenDialog(ProGuardGUI.this); if (returnValue == JFileChooser.APPROVE_OPTION) { loadConfiguration(configurationChooser.getSelectedFile()); } } } /** * This ActionListener saves a ProGuard configuration file based on the * current GUI settings. */ private class MySaveConfigurationActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { configurationChooser.setDialogTitle(msg("saveConfigurationFile")); int returnVal = configurationChooser.showSaveDialog(ProGuardGUI.this); if (returnVal == JFileChooser.APPROVE_OPTION) { saveConfiguration(configurationChooser.getSelectedFile()); } } } /** * This ActionListener displays the ProGuard configuration specified by the * current GUI settings. */ private class MyViewConfigurationActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { // Make sure System.out has not been redirected yet. if (!systemOutRedirected) { consoleTextArea.setText(""); PrintWriter printWriter = new PrintWriter( new TextAreaWriter(consoleTextArea)); try { // TODO: write out relative path names and path names with system properties. // Write the configuration. Configuration configuration = getProGuardConfiguration(); try (ConfigurationWriter configurationWriter = new ConfigurationWriter(printWriter)) { configurationWriter.write(configuration); } } catch (IOException ignore) { // This shouldn't happen. } // Scroll to the top of the configuration. consoleTextArea.setCaretPosition(0); } } } /** * This ActionListener executes ProGuard based on the current GUI settings. */ private class MyProcessActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { // Make sure System.out has not been redirected yet. if (!systemOutRedirected) { systemOutRedirected = true; // Get the informational configuration file name. File configurationFile = configurationChooser.getSelectedFile(); String configurationFileName = configurationFile != null ? configurationFile.getName() : msg("sampleConfigurationFileName"); // Create the ProGuard thread. Thread proGuardThread = new Thread(new ProGuardRunnable(consoleTextArea, getProGuardConfiguration(), configurationFileName)); // Run it. proGuardThread.start(); } } } /** * This ActionListener loads an obfuscated stack trace from a file and puts * it in the proper text area. */ private class MyLoadStackTraceActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { fileChooser.setDialogTitle(msg("selectStackTraceFile")); fileChooser.setSelectedFile(null); int returnValue = fileChooser.showOpenDialog(ProGuardGUI.this); if (returnValue == JFileChooser.APPROVE_OPTION) { loadStackTrace(fileChooser.getSelectedFile()); } } } /** * This ActionListener executes ReTrace based on the current GUI settings. */ private class MyReTraceActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { // Make sure System.out has not been redirected yet. if (!systemOutRedirected) { systemOutRedirected = true; boolean verbose = reTraceVerboseCheckBox.isSelected(); File retraceMappingFile = new File(reTraceMappingTextField.getText()); String stackTrace = stackTraceTextArea.getText(); // Create the ReTrace runnable. Runnable reTraceRunnable = new ReTraceRunnable(reTraceTextArea, verbose, retraceMappingFile, stackTrace); // Run it in this thread, because it won't take long anyway. reTraceRunnable.run(); systemOutRedirected = false; } } } // Small utility methods. /** * Returns the URL for the given path. */ private URL url(String path) { try { return new URL(path); } catch (MalformedURLException e) { try { return new File(path).toURI().toURL(); } catch (MalformedURLException e1) { return null; } } } /** * Returns the path for the given URL, or the empty string if the URL name * is empty. */ private String fileName(URL url) { if (isURL(url)) { if (url.getProtocol().equals("file")) { try { return fileName(new File(url.toURI())); } catch (URISyntaxException ignore) {} } return url.toExternalForm(); } else { return ""; } } /** * Returns whether the given URL is actually a URL, or just a placeholder * for the standard output. */ private boolean isURL(URL url) { return url != null && url.getPath().length() > 0; } /** * Returns the canonical file name for the given file, or the empty string * if the file name is empty. */ private String fileName(File file) { if (file == null) { return ""; } else { try { return file.getCanonicalPath(); } catch (IOException ex) { return file.getPath(); } } } /** * Attaches the tool tip from the GUI resources that corresponds to the * given key, to the given component. */ private static JComponent tip(JComponent component, String messageKey) { component.setToolTipText(msg(messageKey)); return component; } /** * Returns the message from the GUI resources that corresponds to the given * key. */ private static String msg(String messageKey) { return GUIResources.getMessage(messageKey); } /** * Returns the message from the GUI resources that corresponds to the given * key and argument. */ private String msg(String messageKey, Object messageArgument) { return GUIResources.getMessage(messageKey, new Object[] {messageArgument}); } /** * The main method for the ProGuard GUI. */ public static void main(final String[] args) { try { SwingUtil.invokeAndWait(new Runnable() { public void run() { try { ProGuardGUI gui = new ProGuardGUI(); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension guiSize = gui.getSize(); gui.setSize( new Dimension( PREFS.getInt("width", gui.getWidth()), PREFS.getInt("height", gui.getHeight()) ) ); gui.setLocation(new Point( PREFS.getInt("x", (screenSize.width - guiSize.width) / 2), PREFS.getInt("y", (screenSize.height - guiSize.height) / 2) )); gui.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { PREFS.putInt("width", gui.getWidth()); PREFS.putInt("height", gui.getHeight()); PREFS.putInt("x", gui.getLocation().x); PREFS.putInt("y", gui.getLocation().y); } }); gui.show(); // Start the splash animation, unless specified otherwise. int argIndex = 0; if (argIndex < args.length && NO_SPLASH_OPTION.startsWith(args[argIndex])) { gui.skipSplash(); argIndex++; } else { gui.startSplash(); } // Load an initial configuration, if specified. if (argIndex < args.length) { gui.loadConfiguration(new File(args[argIndex])); argIndex++; } if (argIndex < args.length) { System.out.println(gui.getClass().getName() + ": ignoring extra arguments [" + args[argIndex] + "...]"); } } catch (Exception e) { System.out.println("Internal problem starting the ProGuard GUI (" + e.getMessage() + ")"); e.printStackTrace(); } } }); } catch (Exception e) { System.out.println("Internal problem starting the ProGuard GUI (" + e.getMessage() + ")"); e.printStackTrace(); } } } ================================================ FILE: gui/src/proguard/gui/ProGuardRunnable.java ================================================ /* * 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.gui; import proguard.*; import javax.swing.*; import java.awt.*; import java.io.*; /** * This Runnable runs ProGuard, sending console output to a text * area and any exceptions to message dialogs. * * @see ProGuard * @author Eric Lafortune */ final class ProGuardRunnable implements Runnable { private final JTextArea consoleTextArea; private final Configuration configuration; private final String configurationFileName; /** * Creates a new ProGuardRunnable object. * @param consoleTextArea the text area to send the console output to. * @param configuration the ProGuard configuration. * @param configurationFileName the optional file name of the configuration, * for informational purposes. */ public ProGuardRunnable(JTextArea consoleTextArea, Configuration configuration, String configurationFileName) { this.consoleTextArea = consoleTextArea; this.configuration = configuration; this.configurationFileName = configurationFileName; } // Implementation for Runnable. public void run() { consoleTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); consoleTextArea.setText(""); // Redirect the System's out and err streams to the console text area. PrintStream oldOut = System.out; PrintStream oldErr = System.err; PrintWriter outWriter = new PrintWriter(new TextAreaWriter(consoleTextArea), true); PrintStream outStream = new PrintStream(new TextAreaOutputStream(consoleTextArea), true); System.setOut(outStream); System.setErr(outStream); try { // Create a new ProGuard object with the GUI's configuration. ProGuard proGuard = new ProGuard(configuration); // Run it. proGuard.execute(); // Print out the completion message. outWriter.println("Processing completed successfully"); } catch (Exception ex) { //ex.printStackTrace(); // Print out the exception message. outWriter.println(ex.getMessage()); // Show a dialog as well. MessageDialogRunnable.showMessageDialog(consoleTextArea, ex.getMessage(), msg("errorProcessing"), JOptionPane.ERROR_MESSAGE); } catch (OutOfMemoryError er) { // Forget about the ProGuard object as quickly as possible. System.gc(); // Print out a message suggesting what to do next. outWriter.println(msg("outOfMemoryInfo", configurationFileName)); // Show a dialog as well. MessageDialogRunnable.showMessageDialog(consoleTextArea, msg("outOfMemory"), msg("errorProcessing"), JOptionPane.ERROR_MESSAGE); } finally { // Make sure all output has been sent to the console text area. outWriter.close(); // Restore the old System's out and err streams. System.setOut(oldOut); System.setErr(oldErr); } consoleTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); // Reset the global static redirection lock. ProGuardGUI.systemOutRedirected = false; } // Small utility methods. /** * Returns the message from the GUI resources that corresponds to the given * key. */ private String msg(String messageKey) { return GUIResources.getMessage(messageKey); } /** * Returns the message from the GUI resources that corresponds to the given * key and argument. */ private String msg(String messageKey, Object messageArgument) { return GUIResources.getMessage(messageKey, new Object[] {messageArgument}); } } ================================================ FILE: gui/src/proguard/gui/ReTraceRunnable.java ================================================ /* * 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.gui; import proguard.retrace.ReTrace; import javax.swing.*; import java.awt.*; import java.io.*; /** * This Runnable runs ReTrace, sending the output to a text * area and any exceptions to message dialogs. * * @see ReTrace * @author Eric Lafortune */ final class ReTraceRunnable implements Runnable { private final JTextArea consoleTextArea; private final boolean verbose; private final File mappingFile; private final String stackTrace; /** * Creates a new ReTraceRunnable. * @param consoleTextArea the text area to send the console output to. * @param verbose specifies whether the de-obfuscated stack trace * should be verbose. * @param mappingFile the mapping file that was written out by ProGuard. */ public ReTraceRunnable(JTextArea consoleTextArea, boolean verbose, File mappingFile, String stackTrace) { this.consoleTextArea = consoleTextArea; this.verbose = verbose; this.mappingFile = mappingFile; this.stackTrace = stackTrace; } // Implementation for Runnable. public void run() { consoleTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); consoleTextArea.setText(""); LineNumberReader reader = new LineNumberReader( new CharArrayReader(stackTrace.toCharArray())); PrintWriter writer = new PrintWriter(new TextAreaWriter(consoleTextArea), true); try { // Execute ReTrace with the collected settings. new ReTrace(ReTrace.REGULAR_EXPRESSION, ReTrace.REGULAR_EXPRESSION2, true, verbose, mappingFile) .retrace(reader, writer); } catch (Exception ex) { // Print out the exception message. System.out.println(ex.getMessage()); // Show a dialog as well. MessageDialogRunnable.showMessageDialog(consoleTextArea, ex.getMessage(), msg("errorReTracing"), JOptionPane.ERROR_MESSAGE); } catch (OutOfMemoryError er) { // Forget about the ProGuard object as quickly as possible. System.gc(); // Print out a message suggesting what to do next. System.out.println(msg("outOfMemory")); // Show a dialog as well. MessageDialogRunnable.showMessageDialog(consoleTextArea, msg("outOfMemory"), msg("errorReTracing"), JOptionPane.ERROR_MESSAGE); } consoleTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); consoleTextArea.setCaretPosition(0); } // Small utility methods. /** * Returns the message from the GUI resources that corresponds to the given * key. */ private String msg(String messageKey) { return GUIResources.getMessage(messageKey); } } ================================================ FILE: gui/src/proguard/gui/SwingUtil.java ================================================ /* * 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.gui; import javax.swing.*; import java.lang.reflect.InvocationTargetException; /** * This utility class provides variants of the invocation method from the * SwingUtilities class. * * @see SwingUtilities * @author Eric Lafortune */ public class SwingUtil { /** * Invokes the given Runnable in the AWT event dispatching thread, * and waits for it to finish. This method may be called from any thread, * including the event dispatching thread itself. * @see SwingUtilities#invokeAndWait(Runnable) * @param runnable the Runnable to be executed. */ public static void invokeAndWait(Runnable runnable) throws InterruptedException, InvocationTargetException { try { if (SwingUtilities.isEventDispatchThread()) { runnable.run(); } else { SwingUtilities.invokeAndWait(runnable); } } catch (Exception ex) { // Ignore any exceptions. } } /** * Invokes the given Runnable in the AWT event dispatching thread, not * necessarily right away. This method may be called from any thread, * including the event dispatching thread itself. * @see SwingUtilities#invokeLater(Runnable) * @param runnable the Runnable to be executed. */ public static void invokeLater(Runnable runnable) { if (SwingUtilities.isEventDispatchThread()) { runnable.run(); } else { SwingUtilities.invokeLater(runnable); } } } ================================================ FILE: gui/src/proguard/gui/TabbedPane.java ================================================ /* * 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.gui; import javax.swing.*; import java.awt.*; import java.awt.event.*; /** * This Jpanel is similar to a JTabbedPane. * It uses buttons on the left-hand side to switch between panels. * An image can be added below these buttons. * Some methods are provided to switch between tabs. * * @author Eric Lafortune */ public class TabbedPane extends JPanel { private final CardLayout cardLayout = new CardLayout(); private final JPanel cardPanel = new JPanel(cardLayout); private final ButtonGroup buttonGroup = new ButtonGroup(); /** * Creates a new TabbedPane. */ public TabbedPane() { GridBagLayout layout = new GridBagLayout(); setLayout(layout); GridBagConstraints cardConstraints = new GridBagConstraints(); cardConstraints.gridx = 1; cardConstraints.gridy = 0; cardConstraints.gridheight = GridBagConstraints.REMAINDER; cardConstraints.fill = GridBagConstraints.BOTH; cardConstraints.weightx = 1.0; cardConstraints.weighty = 1.0; cardConstraints.anchor = GridBagConstraints.NORTHWEST; add(cardPanel, cardConstraints); } /** * Adds a component with a given title to the tabbed pane. * * @param title the title that will be used in the tab button. * @param component the component that will be added as a tab. */ public Component add(final String title, Component component) { GridBagConstraints buttonConstraints = new GridBagConstraints(); buttonConstraints.gridx = 0; buttonConstraints.fill = GridBagConstraints.HORIZONTAL; buttonConstraints.anchor = GridBagConstraints.NORTHWEST; buttonConstraints.ipadx = 10; buttonConstraints.ipady = 4; JToggleButton button = new JToggleButton(title); // Let the button react on the mouse press, instead of waiting for the // mouse release. button.setModel(new JToggleButton.ToggleButtonModel() { public void setPressed(boolean b) { if ((isPressed() == b) || !isEnabled()) { return; } if (!b && isArmed()) { setSelected(!this.isSelected()); } if (b) { stateMask |= PRESSED; } else { stateMask &= ~PRESSED; } fireStateChanged(); if (isPressed()) { fireActionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getActionCommand())); } } }); // Switch to the tab on a button press. button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cardLayout.show(cardPanel, title); } }); // Only one button can be selected at the same time. buttonGroup.add(button); // If this is the first tab, make sure its button is selected. if (cardPanel.getComponentCount() == 0) { button.setSelected(true); } // Add the button and its panel. add(button, buttonConstraints); cardPanel.add(title, component); return component; } /** * Adds an image below the tab buttons, after all tabs have been added. * The image will only be as visible as permitted by the available space. * * @param image the image. * @return the component containing the image. */ public Component addImage(final Image image) { GridBagConstraints imageConstraints = new GridBagConstraints(); imageConstraints.gridx = 0; imageConstraints.weighty = 1.0; imageConstraints.fill = GridBagConstraints.BOTH; imageConstraints.anchor = GridBagConstraints.SOUTHWEST; JButton component = new JButton(new ImageIcon(image)); component.setFocusPainted(false); component.setFocusable(false); component.setRequestFocusEnabled(false); component.setRolloverEnabled(false); component.setMargin(new Insets(0, 0, 0, 0)); component.setHorizontalAlignment(JButton.LEFT); component.setVerticalAlignment(JButton.BOTTOM); component.setPreferredSize(new Dimension(0, 0)); add(component, imageConstraints); return component; } /** * Selects the first tab. */ public void first() { cardLayout.first(cardPanel); updateButtonSelection(); } /** * Selects the last tab. */ public void last() { cardLayout.last(cardPanel); updateButtonSelection(); } /** * Selects the previous tab. */ public void previous() { cardLayout.previous(cardPanel); updateButtonSelection(); } /** * Selects the next tab. */ public void next() { cardLayout.next(cardPanel); updateButtonSelection(); } /** * Lets the button selection reflect the currently visible panel. */ private void updateButtonSelection() { int count = cardPanel.getComponentCount(); for (int index = 0 ; index < count ; index++) { Component card = cardPanel.getComponent(index); if (card.isShowing()) { JToggleButton button = (JToggleButton)getComponent(index+1); button.setSelected(true); } } } } ================================================ FILE: gui/src/proguard/gui/TextAreaOutputStream.java ================================================ /* * 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.gui; import javax.swing.*; import java.io.*; /** * This PrintStream appends its output to a given text area. * * @author Eric Lafortune */ final class TextAreaOutputStream extends FilterOutputStream implements Runnable { private final JTextArea textArea; public TextAreaOutputStream(JTextArea textArea) { super(new ByteArrayOutputStream()); this.textArea = textArea; } // Implementation for FilterOutputStream. public void flush() throws IOException { super.flush(); try { // Append the accumulated buffer contents to the text area. SwingUtil.invokeAndWait(this); } catch (Exception e) { // Nothing. } } // Implementation for Runnable. public void run() { ByteArrayOutputStream out = (ByteArrayOutputStream)super.out; // Has any new text been written? String text = out.toString(); if (text.length() > 0) { // Append the accumulated text to the text area. textArea.append(text); // Clear the buffer. out.reset(); } } } ================================================ FILE: gui/src/proguard/gui/TextAreaWriter.java ================================================ /* * 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.gui; import javax.swing.*; import java.io.*; /** * This writer appends its output to a given text area. * * @author Eric Lafortune */ final class TextAreaWriter extends FilterWriter implements Runnable { private final JTextArea textArea; public TextAreaWriter(JTextArea textArea) { super(new CharArrayWriter()); this.textArea = textArea; } // Implementation for FilterWriter. public void flush() throws IOException { super.flush(); try { // Append the accumulated buffer contents to the text area. SwingUtil.invokeAndWait(this); } catch (Exception e) { // Nothing. } } // Implementation for Runnable. public void run() { CharArrayWriter writer = (CharArrayWriter)super.out; // Has any new text been written? String text = writer.toString(); if (text.length() > 0) { // Append the accumulated text to the text area. textArea.append(text); // Clear the buffer. writer.reset(); } } } ================================================ FILE: gui/src/proguard/gui/boilerplate.pro ================================================ # Keep - Applications. Keep all application classes, along with their 'main' methods. -keepclasseswithmembers public class * { public static void main(java.lang.String[]); } # Keep - Applets. Keep all extensions of java.applet.Applet. -keep public class * extends java.applet.Applet # Keep - Servlets. Keep all extensions of javax.servlet.Servlet. -keep public class * extends javax.servlet.Servlet # Keep - Midlets. Keep all extensions of javax.microedition.midlet.MIDlet. -keep public class * extends javax.microedition.midlet.MIDlet # Keep - Xlets. Keep all extensions of javax.tv.xlet.Xlet. -keep public class * extends javax.tv.xlet.Xlet # Keep - Libraries. Keep all public and protected classes, fields, and methods. -keep public class * { public protected ; public protected ; } # Keep - Native method names. Keep all native class/method names. -keepclasseswithmembernames,includedescriptorclasses class * { native ; } # Keep - _class method names. Keep all .class method names. This may be # useful for libraries that will be obfuscated again with different obfuscators. -keepclassmembernames class * { java.lang.Class class$(java.lang.String); java.lang.Class class$(java.lang.String,boolean); } # Also keep - Enumerations. Keep the special static methods that are required in # enumeration classes. -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Also keep - Serialization code. Keep all fields and methods that are used for # serialization. -keepclassmembers class * extends java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # Also keep - BeanInfo classes. Keep all implementations of java.beans.BeanInfo. -keep class * implements java.beans.BeanInfo # Also keep - Bean classes. Keep all specified classes, along with their getters # and setters. -keep class * { void set*(***); void set*(int,***); boolean is*(); boolean is*(int); *** get*(); *** get*(int); } # Also keep - Database drivers. Keep all implementations of java.sql.Driver. -keep class * implements java.sql.Driver # Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI, # along with the special 'createUI' method. -keep class * extends javax.swing.plaf.ComponentUI { public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent); } # Also keep - RMI interfaces. Keep all interfaces that extend the # java.rmi.Remote interface, and their methods. -keep interface * extends java.rmi.Remote { ; } # Also keep - RMI implementations. Keep all implementations of java.rmi.Remote, # including any explicit or implicit implementations of Activatable, with their # two-argument constructors. -keep class * implements java.rmi.Remote { (java.rmi.activation.ActivationID,java.rmi.MarshalledObject); } # Android - Android activities. Keep all extensions of Android activities. -keep public class * extends android.app.Activity # Android - Android applications. Keep all extensions of Android applications. -keep public class * extends android.app.Application # Android - Android services. Keep all extensions of Android services. -keep public class * extends android.app.Service # Android - Broadcast receivers. Keep all extensions of Android broadcast receivers. -keep public class * extends android.content.BroadcastReceiver # Android - Content providers. Keep all extensions of Android content providers. -keep public class * extends android.content.ContentProvider # More Android... - View classes. Keep all Android views and their constructors and setters. -keep public class * extends android.view.View { public (android.content.Context); public (android.content.Context, android.util.AttributeSet); public (android.content.Context, android.util.AttributeSet, int); public void set*(...); } # More Android... - Layout classes. Keep classes with constructors that may be referenced from Android layout # files. -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet); } -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet, int); } # More Android... - Contexts. Keep all extensions of Android Context. -keepclassmembers class * extends android.content.Context { public void *(android.view.View); public void *(android.view.MenuItem); } # More Android... - Parcelables. Keep all extensions of Android Parcelables. -keepclassmembers class * implements android.os.Parcelable { static ** CREATOR; } # More Android... - R classes. Keep all fields of Android R classes. -keepclassmembers class **.R$* { public static ; } # Android annotations... - Support annotations. Support annotations for Android. -keep @android.support.annotation.Keep class * -keepclassmembers class * { @android.support.annotation.Keep *; } # Android annotations... - Facebook keep annotations. Keep annotations for Facebook. -keep @com.facebook.proguard.annotations.DoNotStrip class * -keepclassmembers class * { @com.facebook.proguard.annotations.DoNotStrip *; } -keep @com.facebook.proguard.annotations.KeepGettersAndSetters class * -keepclassmembers class * { @com.facebook.proguard.annotations.KeepGettersAndSetters *; } # Android annotations... - ProGuard annotations. Keep annotations for ProGuard. -keep @proguard.annotation.Keep class * -keepclassmembers class * { @proguard.annotation.Keep *; } -keepclassmembernames class * { @proguard.annotation.KeepName *; } -keep class * implements @proguard.annotation.KeepImplementations * -keep public class * implements @proguard.annotation.KeepPublicImplementations * -keepclassmembers @proguard.annotation.KeepClassMembers class * { *; } -keepclassmembers @proguard.annotation.KeepPublicClassMembers class * { public *; } -keepclassmembers @proguard.annotation.KeepPublicProtectedClassMembers class * { public protected *; } -keepclassmembernames @proguard.annotation.KeepClassMemberNames class * { *; } -keepclassmembernames @proguard.annotation.KeepPublicClassMemberNames class * { public *; } -keepclassmembernames @proguard.annotation.KeepPublicProtectedClassMemberNames class * { public protected *; } -keepclassmembers @proguard.annotation.KeepGettersSetters class * { void set*(***); void set*(int, ***); boolean is*(); boolean is*(int); *** get*(); *** get*(int); } -keepclassmembers @proguard.annotation.KeepPublicGettersSetters class * { public void set*(***); public void set*(int, ***); public boolean is*(); public boolean is*(int); public *** get*(); public *** get*(int); } -keepnames @proguard.annotation.KeepName class * # Android annotations... - Google keep annotations. Keep annotations for Google. -keepclassmembernames class * { @com.google.android.gms.common.annotation.KeepName *; } -keepnames @com.google.android.gms.common.annotation.KeepName class * # Android libraries... - Design support libraries. Keep setters for design support libraries. -keep !abstract class android.support.design.widget.* implements android.support.design.widget.CoordinatorLayout$Behavior { (android.content.Context, android.util.AttributeSet); } -keepnames class android.support.design.widget.CoordinatorLayout # Android libraries... - Kotlin. Keep some methods for Kotlin for Android Development. -keepclassmembers,allowshrinking,allowobfuscation class kotlin.jvm.internal.Intrinsics { void throwNpe(); } # Android libraries... - Google Play Services. Keep classes for Google Play Services. -keep class com.google.android.gms.tagmanager.TagManagerService -keep class com.google.android.gms.measurement.AppMeasurement -keep class com.google.android.gms.measurement.AppMeasurementReceiver -keep class com.google.android.gms.measurement.AppMeasurementService -keepclassmembers class com.google.android.gms.ads.identifier.AdvertisingIdClient, com.google.android.gms.ads.identifier.AdvertisingIdClient$Info, com.google.android.gms.common.GooglePlayServicesUtil { public ; } -keep,allowobfuscation class com.google.android.gms.ads.identifier.AdvertisingIdClient, com.google.android.gms.ads.identifier.AdvertisingIdClient$Info, com.google.android.gms.common.GooglePlayServicesUtil -keep,allowshrinking class com.google.android.gms.iid.MessengerCompat, com.google.android.gms.location.ActivityRecognitionResult, com.google.android.gms.maps.GoogleMapOptions -keep,allowshrinking class com.google.android.gms.ads.AdActivity, com.google.android.gms.ads.purchase.InAppPurchaseActivity, com.google.android.gms.gcm.GoogleCloudMessaging, com.google.android.gms.location.places.*Api -keepclassmembers class com.google.android.gms.common.internal.safeparcel.SafeParcelable { public static final java.lang.String NULL; } # Android libraries... - Firebase. Keep classes for Firebase for Android. -keep class com.google.firebase.FirebaseApp -keep class com.google.firebase.auth.FirebaseAuth -keep class com.google.firebase.crash.FirebaseCrash -keep class com.google.firebase.database.connection.idl.IPersistentConnectionImpl -keep class com.google.firebase.iid.FirebaseInstanceId # Android libraries... - Google Cloud Messaging. Keep classes for Google Cloud Messaging. -keep,allowshrinking class **.GCMIntentService # Android libraries... - Guava. Keep classes for the Guava libraries. -keepclassmembers class com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator { sun.misc.Unsafe theUnsafe; } # Android libraries... - RxJava. Keep classes for RxJava. -keepclassmembers class rx.internal.util.unsafe.*Queue { long producerIndex; long consumerIndex; rx.internal.util.atomic.LinkedQueueNode producerNode; rx.internal.util.atomic.LinkedQueueNode consumerNode; } # Android libraries... - ActionBarSherlock. Keep classes for ActionBarSherlock. -keepclassmembers !abstract class * extends com.actionbarsherlock.ActionBarSherlock { (android.app.Activity, int); } # Android libraries... - GSON. Keep classes for the GSON library. -keepclassmembers class * { @com.google.gson.annotations.Expose ; } -keepclassmembers enum * { @com.google.gson.annotations.SerializedName ; } -keepclasseswithmembers,allowobfuscation,includedescriptorclasses class * { @com.google.gson.annotations.Expose ; } -keepclasseswithmembers,allowobfuscation,includedescriptorclasses class * { @com.google.gson.annotations.SerializedName ; } # Android libraries... - Dagger code. Keep the classes that Dagger accesses # by reflection. -keep class **$$ModuleAdapter -keep class **$$InjectAdapter -keep class **$$StaticInjection -if class **$$ModuleAdapter -keep class <1> -if class **$$InjectAdapter -keep class <1> -if class **$$StaticInjection -keep class <1> -keepnames class dagger.Lazy -keepclassmembers,allowobfuscation class * { @dagger.** *; } # Android libraries... - Butterknife code. Keep the classes that Butterknife accesses # by reflection. -keepclasseswithmembers class * { @butterknife.* ; } -keepclasseswithmembers class * { @butterknife.* ; } -keepclasseswithmembers class * { @butterknife.On* ; } -keep class **$$ViewInjector { public static void inject(...); public static void reset(...); } -keep class **$$ViewBinder { public static void bind(...); public static void unbind(...); } -if class **$$ViewBinder -keep class <1> -keep class **_ViewBinding { (<1>, android.view.View); } -if class **_ViewBinding -keep class <1> -keep,allowobfuscation @interface butterknife.* -dontwarn butterknife.internal.ButterKnifeProcessor # Android libraries... - Roboguice. Keep classes for RoboGuice. -keepclassmembers class * implements com.google.inject.Module { (android.content.Context); (); } -keepclassmembers class android.support.v4.app.Fragment { public android.view.View getView(); } -keepclassmembers class android.support.v4.app.FragmentManager { public android.support.v4.app.Fragment findFragmentById(int); public android.support.v4.app.Fragment findFragmentByTag(java.lang.String); } -keep,allowobfuscation class roboguice.activity.event.OnCreateEvent -keep,allowobfuscation class roboguice.inject.SharedPreferencesProvider$PreferencesNameHolder -dontnote com.google.inject.Module # Android libraries... - Otto. Keep classes for Otto. -keepclassmembers,allowobfuscation class * { @com.squareup.otto.* ; } # Android libraries... - Greenrobot EventBus V2. -keepclassmembers class * { public void onEvent*(***); } # Android libraries... - Greenrobot EventBus V3. -keep enum org.greenrobot.eventbus.ThreadMode { *; } -keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { (java.lang.Throwable); } -keep,allowobfuscation class org.greenrobot.eventbus.Subscribe { *; } -keepclassmembers,allowobfuscation class ** { @org.greenrobot.eventbus.Subscribe ; } # Android libraries... - Google API. Keep classes and field for Google API. -keepclassmembers class * { @com.google.api.client.util.Key ; @com.google.api.client.util.Value ; @com.google.api.client.util.NullValue ; } -keep,allowobfuscation class com.google.api.client.util.Types { java.lang.IllegalArgumentException handleExceptionForNewInstance(java.lang.Exception, java.lang.Class); } # Android libraries... - Facebook API. Keep methods for the Facebook API. -keepclassmembers interface com.facebook.model.GraphObject { ; } # Android libraries... - Javascript interfaces. Keep all methods from Android Javascripts. -keepclassmembers class * { @android.webkit.JavascriptInterface ; } # Android libraries... - Compatibility classes. Avoid merging and inlining compatibility classes. -keep,allowshrinking,allowobfuscation class android.support.**Compat*, android.support.**Honeycomb*, android.support.**IceCreamSandwich*, android.support.**JellyBean*, android.support.**Jellybean*, android.support.**JB*, android.support.**KitKat*, android.support.**Kitkat*, android.support.**Lollipop*, android.support.**19, android.support.**20, android.support.**21, android.support.**22, android.support.**23, android.support.**24, android.support.**25, android.support.**Api* { *; } -keep,allowobfuscation,allowshrinking @android.annotation.TargetApi class android.support.** { *; } # Android libraries... - Signatures. Keep setters for signature optimized with class from API level 19 or # higher. -keep,allowshrinking,allowobfuscation class android.support.v4.app.FragmentState$InstantiationException { (...); } -keep,allowshrinking,allowobfuscation class android.support.v4.app.Fragment$InstantiationException { (...); } # Android libraries... - Fields accessed before initialization. Keep fields that are accessed before # initialization. -keepclassmembers,allowshrinking,allowobfuscation class android.support.**,com.google.android.gms.**.internal.** { !static final ; } # Android libraries... - Sensitive classes. Keep sensitive classes. -keep,allowshrinking,allowobfuscation class com.google.android.gms.**.z* { *; } # Android libraries... - Injection. Keep classes for injection in Guice/RoboGuice/Dagger/ActionBarSherlock. -keep,allowobfuscation class * implements com.google.inject.Provider -keep,allowobfuscation @interface javax.inject.** { *; } -keep,allowobfuscation @interface com.google.inject.** { *; } -keep,allowobfuscation @interface roboguice.** { *; } -keep,allowobfuscation @interface com.actionbarsherlock.** { *; } -keepclassmembers,allowobfuscation class * { @javax.inject.** ; @com.google.inject.** ; @roboguice.** ; @roboguice.event.Observes ; @com.actionbarsherlock.** ; @dagger.** *; !private (); @com.google.inject.Inject (...); } # Android libraries... - Retrofit. Keep classes for Retrofit. -keepclassmembers @retrofit.http.RestMethod @interface * { ; } -keepclassmembers,allowobfuscation interface * { @retrofit.http.** ; } -keep,allowobfuscation @retrofit.http.RestMethod @interface * -keep,allowobfuscation @interface retrofit2.http.** # Android libraries... - Google inject. Keep classes for Google inject. -keepclassmembers class * { void finalizeReferent(); } -keepclassmembers class com.google.inject.internal.util.$Finalizer { public static java.lang.ref.ReferenceQueue startFinalizer(java.lang.Class,java.lang.Object); } -keepnames class com.google.inject.internal.util.$FinalizableReference # Android libraries... - Jackson. Keep classes for Jackson. -keepclassmembers class * { @org.codehaus.jackson.annotate.* ; } -keepclassmembers @org.codehaus.jackson.annotate.JsonAutoDetect class * { void set*(***); *** get*(); boolean is*(); } # Android libraries... - Crashlytics. Keep classes for Crashlytics. -keep class * extends io.fabric.sdk.android.Kit # Remove - System method calls. Remove all invocations of System # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.System { public static long currentTimeMillis(); static java.lang.Class getCallerClass(); public static int identityHashCode(java.lang.Object); public static java.lang.SecurityManager getSecurityManager(); public static java.util.Properties getProperties(); public static java.lang.String getProperty(java.lang.String); public static java.lang.String getenv(java.lang.String); public static java.lang.String mapLibraryName(java.lang.String); public static java.lang.String getProperty(java.lang.String,java.lang.String); } # Remove - Math method calls. Remove all invocations of Math # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.Math { public static double sin(double); public static double cos(double); public static double tan(double); public static double asin(double); public static double acos(double); public static double atan(double); public static double toRadians(double); public static double toDegrees(double); public static double exp(double); public static double log(double); public static double log10(double); public static double sqrt(double); public static double cbrt(double); public static double IEEEremainder(double,double); public static double ceil(double); public static double floor(double); public static double rint(double); public static double atan2(double,double); public static double pow(double,double); public static int round(float); public static long round(double); public static double random(); public static int abs(int); public static long abs(long); public static float abs(float); public static double abs(double); public static int max(int,int); public static long max(long,long); public static float max(float,float); public static double max(double,double); public static int min(int,int); public static long min(long,long); public static float min(float,float); public static double min(double,double); public static double ulp(double); public static float ulp(float); public static double signum(double); public static float signum(float); public static double sinh(double); public static double cosh(double); public static double tanh(double); public static double hypot(double,double); public static double expm1(double); public static double log1p(double); } # Remove - Number method calls. Remove all invocations of Number # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.* extends java.lang.Number { public static java.lang.String toString(byte); public static java.lang.Byte valueOf(byte); public static byte parseByte(java.lang.String); public static byte parseByte(java.lang.String,int); public static java.lang.Byte valueOf(java.lang.String,int); public static java.lang.Byte valueOf(java.lang.String); public static java.lang.Byte decode(java.lang.String); public int compareTo(java.lang.Byte); public static java.lang.String toString(short); public static short parseShort(java.lang.String); public static short parseShort(java.lang.String,int); public static java.lang.Short valueOf(java.lang.String,int); public static java.lang.Short valueOf(java.lang.String); public static java.lang.Short valueOf(short); public static java.lang.Short decode(java.lang.String); public static short reverseBytes(short); public int compareTo(java.lang.Short); public static java.lang.String toString(int,int); public static java.lang.String toHexString(int); public static java.lang.String toOctalString(int); public static java.lang.String toBinaryString(int); public static java.lang.String toString(int); public static int parseInt(java.lang.String,int); public static int parseInt(java.lang.String); public static java.lang.Integer valueOf(java.lang.String,int); public static java.lang.Integer valueOf(java.lang.String); public static java.lang.Integer valueOf(int); public static java.lang.Integer getInteger(java.lang.String); public static java.lang.Integer getInteger(java.lang.String,int); public static java.lang.Integer getInteger(java.lang.String,java.lang.Integer); public static java.lang.Integer decode(java.lang.String); public static int highestOneBit(int); public static int lowestOneBit(int); public static int numberOfLeadingZeros(int); public static int numberOfTrailingZeros(int); public static int bitCount(int); public static int rotateLeft(int,int); public static int rotateRight(int,int); public static int reverse(int); public static int signum(int); public static int reverseBytes(int); public int compareTo(java.lang.Integer); public static java.lang.String toString(long,int); public static java.lang.String toHexString(long); public static java.lang.String toOctalString(long); public static java.lang.String toBinaryString(long); public static java.lang.String toString(long); public static long parseLong(java.lang.String,int); public static long parseLong(java.lang.String); public static java.lang.Long valueOf(java.lang.String,int); public static java.lang.Long valueOf(java.lang.String); public static java.lang.Long valueOf(long); public static java.lang.Long decode(java.lang.String); public static java.lang.Long getLong(java.lang.String); public static java.lang.Long getLong(java.lang.String,long); public static java.lang.Long getLong(java.lang.String,java.lang.Long); public static long highestOneBit(long); public static long lowestOneBit(long); public static int numberOfLeadingZeros(long); public static int numberOfTrailingZeros(long); public static int bitCount(long); public static long rotateLeft(long,int); public static long rotateRight(long,int); public static long reverse(long); public static int signum(long); public static long reverseBytes(long); public int compareTo(java.lang.Long); public static java.lang.String toString(float); public static java.lang.String toHexString(float); public static java.lang.Float valueOf(java.lang.String); public static java.lang.Float valueOf(float); public static float parseFloat(java.lang.String); public static boolean isNaN(float); public static boolean isInfinite(float); public static int floatToIntBits(float); public static int floatToRawIntBits(float); public static float intBitsToFloat(int); public static int compare(float,float); public boolean isNaN(); public boolean isInfinite(); public int compareTo(java.lang.Float); public static java.lang.String toString(double); public static java.lang.String toHexString(double); public static java.lang.Double valueOf(java.lang.String); public static java.lang.Double valueOf(double); public static double parseDouble(java.lang.String); public static boolean isNaN(double); public static boolean isInfinite(double); public static long doubleToLongBits(double); public static long doubleToRawLongBits(double); public static double longBitsToDouble(long); public static int compare(double,double); public boolean isNaN(); public boolean isInfinite(); public int compareTo(java.lang.Double); public byte byteValue(); public short shortValue(); public int intValue(); public long longValue(); public float floatValue(); public double doubleValue(); public int compareTo(java.lang.Object); public boolean equals(java.lang.Object); public int hashCode(); public java.lang.String toString(); } # Remove - String method calls. Remove all invocations of String # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.String { public static java.lang.String copyValueOf(char[]); public static java.lang.String copyValueOf(char[],int,int); public static java.lang.String valueOf(boolean); public static java.lang.String valueOf(char); public static java.lang.String valueOf(char[]); public static java.lang.String valueOf(char[],int,int); public static java.lang.String valueOf(double); public static java.lang.String valueOf(float); public static java.lang.String valueOf(int); public static java.lang.String valueOf(java.lang.Object); public static java.lang.String valueOf(long); public boolean contentEquals(java.lang.StringBuffer); public boolean endsWith(java.lang.String); public boolean equalsIgnoreCase(java.lang.String); public boolean equals(java.lang.Object); public boolean matches(java.lang.String); public boolean regionMatches(boolean,int,java.lang.String,int,int); public boolean regionMatches(int,java.lang.String,int,int); public boolean startsWith(java.lang.String); public boolean startsWith(java.lang.String,int); public byte[] getBytes(); public byte[] getBytes(java.lang.String); public char charAt(int); public char[] toCharArray(); public int compareToIgnoreCase(java.lang.String); public int compareTo(java.lang.Object); public int compareTo(java.lang.String); public int hashCode(); public int indexOf(int); public int indexOf(int,int); public int indexOf(java.lang.String); public int indexOf(java.lang.String,int); public int lastIndexOf(int); public int lastIndexOf(int,int); public int lastIndexOf(java.lang.String); public int lastIndexOf(java.lang.String,int); public int length(); public java.lang.CharSequence subSequence(int,int); public java.lang.String concat(java.lang.String); public java.lang.String replaceAll(java.lang.String,java.lang.String); public java.lang.String replace(char,char); public java.lang.String replaceFirst(java.lang.String,java.lang.String); public java.lang.String[] split(java.lang.String); public java.lang.String[] split(java.lang.String,int); public java.lang.String substring(int); public java.lang.String substring(int,int); public java.lang.String toLowerCase(); public java.lang.String toLowerCase(java.util.Locale); public java.lang.String toString(); public java.lang.String toUpperCase(); public java.lang.String toUpperCase(java.util.Locale); public java.lang.String trim(); } # Remove - StringBuffer method calls. Remove all invocations of StringBuffer # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.StringBuffer { public java.lang.String toString(); public char charAt(int); public int capacity(); public int codePointAt(int); public int codePointBefore(int); public int indexOf(java.lang.String,int); public int lastIndexOf(java.lang.String); public int lastIndexOf(java.lang.String,int); public int length(); public java.lang.String substring(int); public java.lang.String substring(int,int); } # Remove - StringBuilder method calls. Remove all invocations of StringBuilder # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.StringBuilder { public java.lang.String toString(); public char charAt(int); public int capacity(); public int codePointAt(int); public int codePointBefore(int); public int indexOf(java.lang.String,int); public int lastIndexOf(java.lang.String); public int lastIndexOf(java.lang.String,int); public int length(); public java.lang.String substring(int); public java.lang.String substring(int,int); } # Remove debugging - Throwable_printStackTrace calls. Remove all invocations of # Throwable.printStackTrace(). -assumenosideeffects public class java.lang.Throwable { public void printStackTrace(); } # Remove debugging - Thread_dumpStack calls. Remove all invocations of # Thread.dumpStack(). -assumenosideeffects public class java.lang.Thread { public static void dumpStack(); } # Remove debugging - All logging API calls. Remove all invocations of the # logging API whose return values are not used. -assumenosideeffects public class java.util.logging.* { ; } # Remove debugging - All Log4j API calls. Remove all invocations of the # Log4j API whose return values are not used. -assumenosideeffects public class org.apache.log4j.** { ; } ================================================ FILE: gui/src/proguard/gui/default.pro ================================================ # The default configuration when starting up the GUI. -libraryjars /lib/rt.jar # Keep - Applications. Keep all application classes, along with their 'main' # methods. -keepclasseswithmembers public class * { public static void main(java.lang.String[]); } # Also keep - Enumerations. Keep the special static methods that are required in # enumeration classes. -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Also keep - Database drivers. Keep all implementations of java.sql.Driver. -keep class * extends java.sql.Driver # Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI, # along with the special 'createUI' method. -keep class * extends javax.swing.plaf.ComponentUI { public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent); } # Keep names - Native method names. Keep all native class/method names. -keepclasseswithmembers,includedescriptorclasses,allowshrinking class * { native ; } # Remove - System method calls. Remove all invocations of System # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.System { public static long currentTimeMillis(); static java.lang.Class getCallerClass(); public static int identityHashCode(java.lang.Object); public static java.lang.SecurityManager getSecurityManager(); public static java.util.Properties getProperties(); public static java.lang.String getProperty(java.lang.String); public static java.lang.String getenv(java.lang.String); public static java.lang.String mapLibraryName(java.lang.String); public static java.lang.String getProperty(java.lang.String,java.lang.String); } # Remove - Math method calls. Remove all invocations of Math # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.Math { public static double sin(double); public static double cos(double); public static double tan(double); public static double asin(double); public static double acos(double); public static double atan(double); public static double toRadians(double); public static double toDegrees(double); public static double exp(double); public static double log(double); public static double log10(double); public static double sqrt(double); public static double cbrt(double); public static double IEEEremainder(double,double); public static double ceil(double); public static double floor(double); public static double rint(double); public static double atan2(double,double); public static double pow(double,double); public static int round(float); public static long round(double); public static double random(); public static int abs(int); public static long abs(long); public static float abs(float); public static double abs(double); public static int max(int,int); public static long max(long,long); public static float max(float,float); public static double max(double,double); public static int min(int,int); public static long min(long,long); public static float min(float,float); public static double min(double,double); public static double ulp(double); public static float ulp(float); public static double signum(double); public static float signum(float); public static double sinh(double); public static double cosh(double); public static double tanh(double); public static double hypot(double,double); public static double expm1(double); public static double log1p(double); } # Remove - Number method calls. Remove all invocations of Number # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.* extends java.lang.Number { public static java.lang.String toString(byte); public static java.lang.Byte valueOf(byte); public static byte parseByte(java.lang.String); public static byte parseByte(java.lang.String,int); public static java.lang.Byte valueOf(java.lang.String,int); public static java.lang.Byte valueOf(java.lang.String); public static java.lang.Byte decode(java.lang.String); public int compareTo(java.lang.Byte); public static java.lang.String toString(short); public static short parseShort(java.lang.String); public static short parseShort(java.lang.String,int); public static java.lang.Short valueOf(java.lang.String,int); public static java.lang.Short valueOf(java.lang.String); public static java.lang.Short valueOf(short); public static java.lang.Short decode(java.lang.String); public static short reverseBytes(short); public int compareTo(java.lang.Short); public static java.lang.String toString(int,int); public static java.lang.String toHexString(int); public static java.lang.String toOctalString(int); public static java.lang.String toBinaryString(int); public static java.lang.String toString(int); public static int parseInt(java.lang.String,int); public static int parseInt(java.lang.String); public static java.lang.Integer valueOf(java.lang.String,int); public static java.lang.Integer valueOf(java.lang.String); public static java.lang.Integer valueOf(int); public static java.lang.Integer getInteger(java.lang.String); public static java.lang.Integer getInteger(java.lang.String,int); public static java.lang.Integer getInteger(java.lang.String,java.lang.Integer); public static java.lang.Integer decode(java.lang.String); public static int highestOneBit(int); public static int lowestOneBit(int); public static int numberOfLeadingZeros(int); public static int numberOfTrailingZeros(int); public static int bitCount(int); public static int rotateLeft(int,int); public static int rotateRight(int,int); public static int reverse(int); public static int signum(int); public static int reverseBytes(int); public int compareTo(java.lang.Integer); public static java.lang.String toString(long,int); public static java.lang.String toHexString(long); public static java.lang.String toOctalString(long); public static java.lang.String toBinaryString(long); public static java.lang.String toString(long); public static long parseLong(java.lang.String,int); public static long parseLong(java.lang.String); public static java.lang.Long valueOf(java.lang.String,int); public static java.lang.Long valueOf(java.lang.String); public static java.lang.Long valueOf(long); public static java.lang.Long decode(java.lang.String); public static java.lang.Long getLong(java.lang.String); public static java.lang.Long getLong(java.lang.String,long); public static java.lang.Long getLong(java.lang.String,java.lang.Long); public static long highestOneBit(long); public static long lowestOneBit(long); public static int numberOfLeadingZeros(long); public static int numberOfTrailingZeros(long); public static int bitCount(long); public static long rotateLeft(long,int); public static long rotateRight(long,int); public static long reverse(long); public static int signum(long); public static long reverseBytes(long); public int compareTo(java.lang.Long); public static java.lang.String toString(float); public static java.lang.String toHexString(float); public static java.lang.Float valueOf(java.lang.String); public static java.lang.Float valueOf(float); public static float parseFloat(java.lang.String); public static boolean isNaN(float); public static boolean isInfinite(float); public static int floatToIntBits(float); public static int floatToRawIntBits(float); public static float intBitsToFloat(int); public static int compare(float,float); public boolean isNaN(); public boolean isInfinite(); public int compareTo(java.lang.Float); public static java.lang.String toString(double); public static java.lang.String toHexString(double); public static java.lang.Double valueOf(java.lang.String); public static java.lang.Double valueOf(double); public static double parseDouble(java.lang.String); public static boolean isNaN(double); public static boolean isInfinite(double); public static long doubleToLongBits(double); public static long doubleToRawLongBits(double); public static double longBitsToDouble(long); public static int compare(double,double); public boolean isNaN(); public boolean isInfinite(); public int compareTo(java.lang.Double); public byte byteValue(); public short shortValue(); public int intValue(); public long longValue(); public float floatValue(); public double doubleValue(); public int compareTo(java.lang.Object); public boolean equals(java.lang.Object); public int hashCode(); public java.lang.String toString(); } # Remove - String method calls. Remove all invocations of String # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.String { public static java.lang.String copyValueOf(char[]); public static java.lang.String copyValueOf(char[],int,int); public static java.lang.String valueOf(boolean); public static java.lang.String valueOf(char); public static java.lang.String valueOf(char[]); public static java.lang.String valueOf(char[],int,int); public static java.lang.String valueOf(double); public static java.lang.String valueOf(float); public static java.lang.String valueOf(int); public static java.lang.String valueOf(java.lang.Object); public static java.lang.String valueOf(long); public boolean contentEquals(java.lang.StringBuffer); public boolean endsWith(java.lang.String); public boolean equalsIgnoreCase(java.lang.String); public boolean equals(java.lang.Object); public boolean matches(java.lang.String); public boolean regionMatches(boolean,int,java.lang.String,int,int); public boolean regionMatches(int,java.lang.String,int,int); public boolean startsWith(java.lang.String); public boolean startsWith(java.lang.String,int); public byte[] getBytes(); public byte[] getBytes(java.lang.String); public char charAt(int); public char[] toCharArray(); public int compareToIgnoreCase(java.lang.String); public int compareTo(java.lang.Object); public int compareTo(java.lang.String); public int hashCode(); public int indexOf(int); public int indexOf(int,int); public int indexOf(java.lang.String); public int indexOf(java.lang.String,int); public int lastIndexOf(int); public int lastIndexOf(int,int); public int lastIndexOf(java.lang.String); public int lastIndexOf(java.lang.String,int); public int length(); public java.lang.CharSequence subSequence(int,int); public java.lang.String concat(java.lang.String); public java.lang.String replaceAll(java.lang.String,java.lang.String); public java.lang.String replace(char,char); public java.lang.String replaceFirst(java.lang.String,java.lang.String); public java.lang.String[] split(java.lang.String); public java.lang.String[] split(java.lang.String,int); public java.lang.String substring(int); public java.lang.String substring(int,int); public java.lang.String toLowerCase(); public java.lang.String toLowerCase(java.util.Locale); public java.lang.String toString(); public java.lang.String toUpperCase(); public java.lang.String toUpperCase(java.util.Locale); public java.lang.String trim(); } # Remove - StringBuffer method calls. Remove all invocations of StringBuffer # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.StringBuffer { public java.lang.String toString(); public char charAt(int); public int capacity(); public int codePointAt(int); public int codePointBefore(int); public int indexOf(java.lang.String,int); public int lastIndexOf(java.lang.String); public int lastIndexOf(java.lang.String,int); public int length(); public java.lang.String substring(int); public java.lang.String substring(int,int); } # Remove - StringBuilder method calls. Remove all invocations of StringBuilder # methods without side effects whose return values are not used. -assumenosideeffects public class java.lang.StringBuilder { public java.lang.String toString(); public char charAt(int); public int capacity(); public int codePointAt(int); public int codePointBefore(int); public int indexOf(java.lang.String,int); public int lastIndexOf(java.lang.String); public int lastIndexOf(java.lang.String,int); public int length(); public java.lang.String substring(int); public java.lang.String substring(int,int); } ================================================ FILE: gui/src/proguard/gui/package.html ================================================ This package contains a GUI for ProGuard and ReTrace. ================================================ FILE: gui/src/proguard/gui/splash/BufferedSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; import java.awt.image.BufferedImage; /** * This Sprite encapsulates another Sprite, which is then buffered in an Image. * * @author Eric Lafortune */ public class BufferedSprite implements Sprite { private final int bufferX; private final int bufferY; private final Image bufferImage; private final Color backgroundColor; private final Sprite sprite; private final VariableInt x; private final VariableInt y; private long cachedTime = -1; /** * Creates a new BufferedSprite with an ABGR image. * @param bufferX the x offset of the buffer image. * @param bufferY the y offset of the buffer image. * @param width the width of the buffer image. * @param height the height of the buffer image. * @param sprite the Sprite that is painted in the buffer. * @param x the variable x ordinate of the image buffer for painting. * @param y the variable y ordinate of the image buffer for painting. * */ public BufferedSprite(int bufferX, int bufferY, int width, int height, Sprite sprite, VariableInt x, VariableInt y) { this(bufferX, bufferY, new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR), null, sprite, x, y); } /** * Creates a new BufferedSprite with the given image. * @param bufferX the x offset of the buffer image. * @param bufferY the y offset of the buffer image. * @param bufferImage the Image that is used for the buffering. * @param backgroundColor the background color that is used for the buffer. * @param sprite the Sprite that is painted in the buffer. * @param x the variable x ordinate of the image buffer for * painting. * @param y the variable y ordinate of the image buffer for * painting. */ public BufferedSprite(int bufferX, int bufferY, Image bufferImage, Color backgroundColor, Sprite sprite, VariableInt x, VariableInt y) { this.bufferX = bufferX; this.bufferY = bufferY; this.bufferImage = bufferImage; this.backgroundColor = backgroundColor; this.sprite = sprite; this.x = x; this.y = y; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { if (time != cachedTime) { Graphics bufferGraphics = bufferImage.getGraphics(); // Clear the background. if (backgroundColor != null) { Graphics2D bufferGraphics2D = (Graphics2D)bufferGraphics; bufferGraphics2D.setComposite(AlphaComposite.Clear); bufferGraphics.fillRect(0, 0, bufferImage.getWidth(null), bufferImage.getHeight(null)); bufferGraphics2D.setComposite(AlphaComposite.Src); } else { bufferGraphics.setColor(backgroundColor); bufferGraphics.fillRect(0, 0, bufferImage.getWidth(null), bufferImage.getHeight(null)); } // Set up the buffer graphics. bufferGraphics.translate(-bufferX, -bufferY); bufferGraphics.setColor(graphics.getColor()); bufferGraphics.setFont(graphics.getFont()); // Draw the sprite. sprite.paint(bufferGraphics, time); bufferGraphics.dispose(); cachedTime = time; } // Draw the buffer image. graphics.drawImage(bufferImage, bufferX + x.getInt(time), bufferY + y.getInt(time), null); } } ================================================ FILE: gui/src/proguard/gui/splash/CircleSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This Sprite represents an animated circle. It can optionally be filled. * * @author Eric Lafortune */ public class CircleSprite implements Sprite { private final boolean filled; private final VariableInt x; private final VariableInt y; private final VariableInt radius; /** * Creates a new CircleSprite. * @param filled specifies whether the rectangle should be filled. * @param x the variable x-coordinate of the center of the circle. * @param y the variable y-coordinate of the center of the circle. * @param radius the variable radius of the circle. */ public CircleSprite(boolean filled, VariableInt x, VariableInt y, VariableInt radius) { this.filled = filled; this.x = x; this.y = y; this.radius = radius; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { int xt = x.getInt(time); int yt = y.getInt(time); int r = radius.getInt(time); if (filled) { graphics.fillOval(xt - r, yt - r, 2 * r, 2 * r); } else { graphics.drawOval(xt - r, yt - r, 2 * r, 2 * r); } } } ================================================ FILE: gui/src/proguard/gui/splash/ClipSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This Sprite encapsulates another Sprite, which is clipped by a clip Sprite. * * @author Eric Lafortune */ public class ClipSprite implements Sprite { private final VariableColor insideClipColor; private final VariableColor outsideClipColor; private final Sprite clipSprite; private final Sprite sprite; /** * Creates a new ClipSprite. * @param insideClipColor the background color inside the clip sprite. * @param outsideClipColor the background color outside the clip sprite. * @param clipSprite the clip Sprite. * @param sprite the clipped Sprite. */ public ClipSprite(VariableColor insideClipColor, VariableColor outsideClipColor, Sprite clipSprite, Sprite sprite) { this.insideClipColor = insideClipColor; this.outsideClipColor = outsideClipColor; this.clipSprite = clipSprite; this.sprite = sprite; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { // Clear the background. Color outsideColor = outsideClipColor.getColor(time); Rectangle clip = graphics.getClipBounds(); graphics.setPaintMode(); graphics.setColor(outsideColor); graphics.fillRect(0, 0, clip.width, clip.height); // Draw the sprite in XOR mode. OverrideGraphics2D g = new OverrideGraphics2D((Graphics2D)graphics); Color insideColor = insideClipColor.getColor(time); g.setOverrideXORMode(insideColor); sprite.paint(g, time); g.setOverrideXORMode(null); // Clear the clip area. g.setOverrideColor(insideColor); clipSprite.paint(g, time); g.setOverrideColor(null); // Draw the sprite in XOR mode. g.setOverrideXORMode(insideColor); sprite.paint(g, time); g.setOverrideXORMode(null); } } ================================================ FILE: gui/src/proguard/gui/splash/ColorSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This Sprite colors another given sprite. * * @author Eric Lafortune */ public class ColorSprite implements Sprite { private final VariableColor color; private final Sprite sprite; /** * Creates a new ColorSprite. * @param color the variable color of the given sprite. * @param sprite the sprite that will be colored and painted. */ public ColorSprite(VariableColor color, Sprite sprite) { this.color = color; this.sprite = sprite; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { // Save the old color. Color oldColor = graphics.getColor(); // Set the new color. graphics.setColor(color.getColor(time)); // Paint the actual sprite. sprite.paint(graphics, time); // Restore the old color. graphics.setColor(oldColor); } } ================================================ FILE: gui/src/proguard/gui/splash/CompositeSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This Sprite is the composition of a list of Sprite objects. * * @author Eric Lafortune */ public class CompositeSprite implements Sprite { private final Sprite[] sprites; /** * Creates a new CompositeSprite. * @param sprites the array of Sprite objects to which the painting will * be delegated, starting with the first element. */ public CompositeSprite(Sprite[] sprites) { this.sprites = sprites; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { // Draw the sprites. for (int index = 0; index < sprites.length; index++) { sprites[index].paint(graphics, time); } } } ================================================ FILE: gui/src/proguard/gui/splash/ConstantColor.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This VariableColor is constant over time. * * @author Eric Lafortune */ public class ConstantColor implements VariableColor { private final Color value; /** * Creates a new ConstantColor. * @param value the constant value. */ public ConstantColor(Color value) { this.value = value; } // Implementation for VariableColor. public Color getColor(long time) { return value; } } ================================================ FILE: gui/src/proguard/gui/splash/ConstantDouble.java ================================================ /* * 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.gui.splash; /** * This VariableDouble is constant over time. * * @author Eric Lafortune */ public class ConstantDouble implements VariableDouble { private final double value; /** * Creates a new ConstantDouble. * @param value the constant value. */ public ConstantDouble(double value) { this.value = value; } // Implementation for VariableDouble. public double getDouble(long time) { return value; } } ================================================ FILE: gui/src/proguard/gui/splash/ConstantFont.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This VariableFont is constant over time. * * @author Eric Lafortune */ public class ConstantFont implements VariableFont { private final Font value; public ConstantFont(Font value) { this.value = value; } // Implementation for VariableFont. public Font getFont(long time) { return value; } } ================================================ FILE: gui/src/proguard/gui/splash/ConstantInt.java ================================================ /* * 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.gui.splash; /** * This VariableInt is constant over time. * * @author Eric Lafortune */ public class ConstantInt implements VariableInt { private final int value; /** * Creates a new ConstantInt. * @param value the constant value. */ public ConstantInt(int value) { this.value = value; } // Implementation for VariableInt. public int getInt(long time) { return value; } } ================================================ FILE: gui/src/proguard/gui/splash/ConstantString.java ================================================ /* * 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.gui.splash; /** * This VariableString is constant over time. * * @author Eric Lafortune */ public class ConstantString implements VariableString { private final String value; /** * Creates a new ConstantString. * @param value the constant value. */ public ConstantString(String value) { this.value = value; } // Implementation for VariableString. public String getString(long time) { return value; } } ================================================ FILE: gui/src/proguard/gui/splash/ConstantTiming.java ================================================ /* * 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.gui.splash; /** * This Timing is constant over time. * * @author Eric Lafortune */ public class ConstantTiming implements Timing { private final double timing; /** * Creates a new ConstantTiming with a value of 0. */ public ConstantTiming() { this(0.0); } /** * Creates a new ConstantTiming with a given value. * @param timing the constant value of the timing. */ public ConstantTiming(double timing) { this.timing = timing; } // Implementation for Timing. public double getTiming(long time) { return timing; } } ================================================ FILE: gui/src/proguard/gui/splash/FontSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This Sprite sets the font for another given sprite. * * @author Eric Lafortune */ public class FontSprite implements Sprite { private final VariableFont font; private final Sprite sprite; /** * Creates a new FontSprite. * @param font the variable Font of the given sprite. * @param sprite the sprite that will be provided of a font and painted. */ public FontSprite(VariableFont font, Sprite sprite) { this.font = font; this.sprite = sprite; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { // Save the old font. Font oldFont = graphics.getFont(); // Set the new font. graphics.setFont(font.getFont(time)); // Paint the actual sprite. sprite.paint(graphics, time); // Restore the old font. graphics.setFont(oldFont); } } ================================================ FILE: gui/src/proguard/gui/splash/ImageSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This Sprite represents an animated image. * * @author Eric Lafortune */ public class ImageSprite implements Sprite { private final Image image; private final VariableInt x; private final VariableInt y; private final VariableDouble scaleX; private final VariableDouble scaleY; /** * Creates a new ImageSprite. * @param image the Image to be painted. * @param x the variable x-coordinate of the upper-left corner of the image. * @param y the variable y-coordinate of the upper-left corner of the image. * @param scaleX the variable x-scale of the image. * @param scaleY the variable y-scale of the image. */ public ImageSprite(Image image, VariableInt x, VariableInt y, VariableDouble scaleX, VariableDouble scaleY) { this.image = image; this.x = x; this.y = y; this.scaleX = scaleX; this.scaleY = scaleY; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { int xt = x.getInt(time); int yt = y.getInt(time); double scale_x = scaleX.getDouble(time); double scale_y = scaleY.getDouble(time); int width = (int)(image.getWidth(null) * scale_x); int height = (int)(image.getHeight(null) * scale_y); graphics.drawImage(image, xt, yt, width, height, null); } } ================================================ FILE: gui/src/proguard/gui/splash/LinearColor.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This VariableColor varies linearly with respect to its Timing. * * @author Eric Lafortune */ public class LinearColor implements VariableColor { private final Color fromValue; private final Color toValue; private final Timing timing; private double cachedTiming = -1.0; private Color cachedColor; /** * Creates a new LinearColor. * @param fromValue the value that corresponds to a timing of 0. * @param toValue the value that corresponds to a timing of 1. * @param timing the applied timing. */ public LinearColor(Color fromValue, Color toValue, Timing timing) { this.fromValue = fromValue; this.toValue = toValue; this.timing = timing; } // Implementation for VariableColor. public Color getColor(long time) { double t = timing.getTiming(time); if (t != cachedTiming) { cachedTiming = t; cachedColor = t == 0.0 ? fromValue : t == 1.0 ? toValue : new Color((int)(fromValue.getRed() + t * (toValue.getRed() - fromValue.getRed())), (int)(fromValue.getGreen() + t * (toValue.getGreen() - fromValue.getGreen())), (int)(fromValue.getBlue() + t * (toValue.getBlue() - fromValue.getBlue()))); } return cachedColor; } } ================================================ FILE: gui/src/proguard/gui/splash/LinearDouble.java ================================================ /* * 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.gui.splash; /** * This VariableDouble varies linearly with respect to its Timing. * * @author Eric Lafortune */ public class LinearDouble implements VariableDouble { private final double fromValue; private final double toValue; private final Timing timing; /** * Creates a new LinearDouble. * @param fromValue the value that corresponds to a timing of 0. * @param toValue the value that corresponds to a timing of 1. * @param timing the applied timing. */ public LinearDouble(double fromValue, double toValue, Timing timing) { this.fromValue = fromValue; this.toValue = toValue; this.timing = timing; } // Implementation for VariableDouble. public double getDouble(long time) { return fromValue + timing.getTiming(time) * (toValue - fromValue); } } ================================================ FILE: gui/src/proguard/gui/splash/LinearInt.java ================================================ /* * 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.gui.splash; /** * This VariableColor varies linearly with respect to its Timing. * * @author Eric Lafortune */ public class LinearInt implements VariableInt { private final int fromValue; private final int toValue; private final Timing timing; /** * Creates a new LinearInt. * @param fromValue the value that corresponds to a timing of 0. * @param toValue the value that corresponds to a timing of 1. * @param timing the applied timing. */ public LinearInt(int fromValue, int toValue, Timing timing) { this.fromValue = fromValue; this.toValue = toValue; this.timing = timing; } // Implementation for VariableInt. public int getInt(long time) { return (int) (fromValue + timing.getTiming(time) * (toValue - fromValue)); } } ================================================ FILE: gui/src/proguard/gui/splash/LinearTiming.java ================================================ /* * 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.gui.splash; /** * This Timing ramps up linearly from 0 to 1 in a given time interval. * * @author Eric Lafortune */ public class LinearTiming implements Timing { private final long fromTime; private final long toTime; /** * Creates a new LinearTiming. * @param fromTime the time at which the timing starts ramping up from 0. * @param toTime the time at which the timing stops ramping up at 1. */ public LinearTiming(long fromTime, long toTime) { this.fromTime = fromTime; this.toTime = toTime; } // Implementation for Timing. public double getTiming(long time) { // Compute the clamped linear interpolation. return time <= fromTime ? 0.0 : time >= toTime ? 1.0 : (double)(time - fromTime) / (double)(toTime - fromTime); } } ================================================ FILE: gui/src/proguard/gui/splash/OverrideGraphics2D.java ================================================ /* * 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.gui.splash; import java.awt.*; import java.awt.RenderingHints.Key; import java.awt.font.*; import java.awt.geom.AffineTransform; import java.awt.image.*; import java.awt.image.renderable.RenderableImage; import java.text.AttributedCharacterIterator; import java.util.Map; /** * This Graphics2D allows to fix some basic settings (Color, Font, Paint, Stroke, * XORMode) of a delegate Graphics2D, overriding any subsequent attempts to * change those settings. * * @author Eric Lafortune * @noinspection deprecation */ final class OverrideGraphics2D extends Graphics2D { private final Graphics2D graphics; private Color overrideColor; private Font overrideFont; private Paint overridePaint; private Stroke overrideStroke; private Color overrideXORMode; private Color color; private Font font; private Paint paint; private Stroke stroke; /** * Creates a new OverrideGraphics2D. * @param graphics the delegate Graphics2D. */ public OverrideGraphics2D(Graphics2D graphics) { this.graphics = graphics; this.color = graphics.getColor(); this.font = graphics.getFont(); this.paint = graphics.getPaint(); this.stroke = graphics.getStroke(); } /** * Fixes the Color of the Graphics2D. * * @param color the fixed Color, or null to undo the fixing. */ public void setOverrideColor(Color color) { this.overrideColor = color; graphics.setColor(color != null ? color : this.color); } /** * Fixes the Font of the Graphics2D. * * @param font the fixed Font, or null to undo the fixing. */ public void setOverrideFont(Font font) { this.overrideFont = font; graphics.setFont(font != null ? font : this.font); } /** * Fixes the Paint of the Graphics2D. * * @param paint the fixed Paint, or null to undo the fixing. */ public void setOverridePaint(Paint paint) { this.overridePaint = paint; graphics.setPaint(paint != null ? paint : this.paint); } /** * Fixes the Stroke of the Graphics2D. * * @param stroke the fixed Stroke, or null to undo the fixing. */ public void setOverrideStroke(Stroke stroke) { this.overrideStroke = stroke; graphics.setStroke(stroke != null ? stroke : this.stroke); } /** * Fixes the XORMode of the Graphics2D. * * @param color the fixed XORMode Color, or null to undo the fixing. */ public void setOverrideXORMode(Color color) { this.overrideXORMode = color; if (color != null) { graphics.setXORMode(color); } else { graphics.setPaintMode(); } } // Implementations for Graphics2D. public void setColor(Color color) { this.color = color; if (overrideColor == null) { graphics.setColor(color); } } public void setFont(Font font) { this.font = font; if (overrideFont == null) { graphics.setFont(font); } } public void setPaint(Paint paint) { this.paint = paint; if (overridePaint == null) { graphics.setPaint(paint); } } public void setStroke(Stroke stroke) { this.stroke = stroke; if (overrideStroke == null) { graphics.setStroke(stroke); } } public void setXORMode(Color color) { if (overrideXORMode == null) { graphics.setXORMode(color); } } public void setPaintMode() { if (overrideXORMode == null) { graphics.setPaintMode(); } } public Color getColor() { return overrideColor != null ? color : graphics.getColor(); } public Font getFont() { return overrideFont != null ? font : graphics.getFont(); } public Paint getPaint() { return overridePaint != null ? paint : graphics.getPaint(); } public Stroke getStroke() { return overrideStroke != null ? stroke : graphics.getStroke(); } public Graphics create() { OverrideGraphics2D g = new OverrideGraphics2D((Graphics2D)graphics.create()); g.setOverrideColor(overrideColor); g.setOverrideFont(overrideFont); g.setOverridePaint(overridePaint); g.setOverrideStroke(overrideStroke); return g; } public Graphics create(int x, int y, int width, int height) { OverrideGraphics2D g = new OverrideGraphics2D((Graphics2D)graphics.create(x, y, width, height)); g.setOverrideColor(overrideColor); g.setOverrideFont(overrideFont); g.setOverridePaint(overridePaint); g.setOverrideStroke(overrideStroke); return g; } // Delegation for Graphics2D public void addRenderingHints(Map hints) { graphics.addRenderingHints(hints); } public void clearRect(int x, int y, int width, int height) { graphics.clearRect(x, y, width, height); } public void clip(Shape s) { graphics.clip(s); } public void clipRect(int x, int y, int width, int height) { graphics.clipRect(x, y, width, height); } public void copyArea(int x, int y, int width, int height, int dx, int dy) { graphics.copyArea(x, y, width, height, dx, dy); } public void dispose() { graphics.dispose(); } public void draw(Shape s) { graphics.draw(s); } public void draw3DRect(int x, int y, int width, int height, boolean raised) { graphics.draw3DRect(x, y, width, height, raised); } public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { graphics.drawArc(x, y, width, height, startAngle, arcAngle); } public void drawBytes(byte[] data, int offset, int length, int x, int y) { graphics.drawBytes(data, offset, length, x, y); } public void drawChars(char[] data, int offset, int length, int x, int y) { graphics.drawChars(data, offset, length, x, y); } public void drawGlyphVector(GlyphVector g, float x, float y) { graphics.drawGlyphVector(g, x, y); } public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) { return graphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); } public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { return graphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer); } public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { return graphics.drawImage(img, x, y, width, height, bgcolor, observer); } public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { return graphics.drawImage(img, x, y, width, height, observer); } public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { return graphics.drawImage(img, x, y, bgcolor, observer); } public boolean drawImage(Image img, int x, int y, ImageObserver observer) { return graphics.drawImage(img, x, y, observer); } public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { return graphics.drawImage(img, xform, obs); } public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { graphics.drawImage(img, op, x, y); } public void drawLine(int x1, int y1, int x2, int y2) { graphics.drawLine(x1, y1, x2, y2); } public void drawOval(int x, int y, int width, int height) { graphics.drawOval(x, y, width, height); } public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { graphics.drawPolygon(xPoints, yPoints, nPoints); } public void drawPolygon(Polygon p) { graphics.drawPolygon(p); } public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { graphics.drawPolyline(xPoints, yPoints, nPoints); } public void drawRect(int x, int y, int width, int height) { graphics.drawRect(x, y, width, height); } public void drawRenderableImage(RenderableImage img, AffineTransform xform) { graphics.drawRenderableImage(img, xform); } public void drawRenderedImage(RenderedImage img, AffineTransform xform) { graphics.drawRenderedImage(img, xform); } public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { graphics.drawRoundRect(x, y, width, height, arcWidth, arcHeight); } public void drawString(String s, float x, float y) { graphics.drawString(s, x, y); } public void drawString(String str, int x, int y) { graphics.drawString(str, x, y); } public void drawString(AttributedCharacterIterator iterator, float x, float y) { graphics.drawString(iterator, x, y); } public void drawString(AttributedCharacterIterator iterator, int x, int y) { graphics.drawString(iterator, x, y); } public boolean equals(Object obj) { return graphics.equals(obj); } public void fill(Shape s) { graphics.fill(s); } public void fill3DRect(int x, int y, int width, int height, boolean raised) { graphics.fill3DRect(x, y, width, height, raised); } public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { graphics.fillArc(x, y, width, height, startAngle, arcAngle); } public void fillOval(int x, int y, int width, int height) { graphics.fillOval(x, y, width, height); } public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { graphics.fillPolygon(xPoints, yPoints, nPoints); } public void fillPolygon(Polygon p) { graphics.fillPolygon(p); } public void fillRect(int x, int y, int width, int height) { graphics.fillRect(x, y, width, height); } public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { graphics.fillRoundRect(x, y, width, height, arcWidth, arcHeight); } public Color getBackground() { return graphics.getBackground(); } public Shape getClip() { return graphics.getClip(); } public Rectangle getClipBounds() { return graphics.getClipBounds(); } public Rectangle getClipBounds(Rectangle r) { return graphics.getClipBounds(r); } public Rectangle getClipRect() { return graphics.getClipRect(); } public Composite getComposite() { return graphics.getComposite(); } public GraphicsConfiguration getDeviceConfiguration() { return graphics.getDeviceConfiguration(); } public FontMetrics getFontMetrics() { return graphics.getFontMetrics(); } public FontMetrics getFontMetrics(Font f) { return graphics.getFontMetrics(f); } public FontRenderContext getFontRenderContext() { return graphics.getFontRenderContext(); } public Object getRenderingHint(Key hintKey) { return graphics.getRenderingHint(hintKey); } public RenderingHints getRenderingHints() { return graphics.getRenderingHints(); } public AffineTransform getTransform() { return graphics.getTransform(); } public int hashCode() { return graphics.hashCode(); } public boolean hit(Rectangle rect, Shape s, boolean onStroke) { return graphics.hit(rect, s, onStroke); } public boolean hitClip(int x, int y, int width, int height) { return graphics.hitClip(x, y, width, height); } public void rotate(double theta) { graphics.rotate(theta); } public void rotate(double theta, double x, double y) { graphics.rotate(theta, x, y); } public void scale(double sx, double sy) { graphics.scale(sx, sy); } public void setBackground(Color color) { graphics.setBackground(color); } public void setClip(int x, int y, int width, int height) { graphics.setClip(x, y, width, height); } public void setClip(Shape clip) { graphics.setClip(clip); } public void setComposite(Composite comp) { graphics.setComposite(comp); } public void setRenderingHint(Key hintKey, Object hintValue) { graphics.setRenderingHint(hintKey, hintValue); } public void setRenderingHints(Map hints) { graphics.setRenderingHints(hints); } public void setTransform(AffineTransform Tx) { graphics.setTransform(Tx); } public void shear(double shx, double shy) { graphics.shear(shx, shy); } public String toString() { return graphics.toString(); } public void transform(AffineTransform Tx) { graphics.transform(Tx); } public void translate(double tx, double ty) { graphics.translate(tx, ty); } public void translate(int x, int y) { graphics.translate(x, y); } } ================================================ FILE: gui/src/proguard/gui/splash/RectangleSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This Sprite represents an animated rounded rectangle. It can optionally be filled. * * @author Eric Lafortune */ public class RectangleSprite implements Sprite { private final boolean filled; private final VariableColor color; private final VariableInt x; private final VariableInt y; private final VariableInt width; private final VariableInt height; private final VariableInt arcWidth; private final VariableInt arcHeight; /** * Creates a new rectangular RectangleSprite. * @param filled specifies whether the rectangle should be filled. * @param color the variable color of the rectangle. * @param x the variable x-ordinate of the upper-left corner of the rectangle. * @param y the variable y-ordinate of the upper-left corner of the rectangle. * @param width the variable width of the rectangle. * @param height the variable height of the rectangle. */ public RectangleSprite(boolean filled, VariableColor color, VariableInt x, VariableInt y, VariableInt width, VariableInt height) { this(filled, color, x, y, width, height, new ConstantInt(0), new ConstantInt(0)); } /** * Creates a new RectangleSprite with rounded corners. * @param filled specifies whether the rectangle should be filled. * @param color the variable color of the rectangle. * @param x the variable x-ordinate of the upper-left corner of the rectangle. * @param y the variable y-ordinate of the upper-left corner of the rectangle. * @param width the variable width of the rectangle. * @param height the variable height of the rectangle. * @param arcWidth the variable width of the corner arcs. * @param arcHeight the variable height of the corner arcs. */ public RectangleSprite(boolean filled, VariableColor color, VariableInt x, VariableInt y, VariableInt width, VariableInt height, VariableInt arcWidth, VariableInt arcHeight) { this.filled = filled; this.color = color; this.x = x; this.y = y; this.width = width; this.height = height; this.arcWidth = arcWidth; this.arcHeight = arcHeight; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { graphics.setColor(color.getColor(time)); int xt = x.getInt(time); int yt = y.getInt(time); int w = width.getInt(time); int h = height.getInt(time); int aw = arcWidth.getInt(time); int ah = arcHeight.getInt(time); if (filled) { graphics.fillRoundRect(xt, yt, w, h, aw, ah); } else { graphics.drawRoundRect(xt, yt, w, h, aw, ah); } } } ================================================ FILE: gui/src/proguard/gui/splash/SawToothTiming.java ================================================ /* * 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.gui.splash; /** * This Timing ramps up linearly from 0 to 1 in a given repeated time interval. * * @author Eric Lafortune */ public class SawToothTiming implements Timing { private final long period; private final long phase; /** * Creates a new SawToothTiming. * @param period the time period for a full cycle. * @param phase the phase of the cycle, which is added to the actual time. */ public SawToothTiming(long period, long phase) { this.period = period; this.phase = phase; } // Implementation for Timing. public double getTiming(long time) { // Compute the translated and scaled saw-tooth function. return (double)((time + phase) % period) / (double)period; } } ================================================ FILE: gui/src/proguard/gui/splash/ShadowedSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This Sprite adds a drop shadow to another Sprite. * * @author Eric Lafortune */ public class ShadowedSprite implements Sprite { private final VariableInt xOffset; private final VariableInt yOffset; private final VariableDouble alpha; private final VariableInt blur; private final Sprite sprite; private float cachedAlpha = -1.0f; private Color cachedColor; /** * Creates a new ShadowedSprite. * @param xOffset the variable x-offset of the shadow, relative to the sprite itself. * @param yOffset the variable y-offset of the shadow, relative to the sprite itself. * @param alpha the variable darkness of the shadow (between 0 and 1). * @param blur the variable blur of the shadow (0 for sharp shadows, 1 or * more for increasingly blurry shadows). * @param sprite the Sprite to be painted with its shadow. */ public ShadowedSprite(VariableInt xOffset, VariableInt yOffset, VariableDouble alpha, VariableInt blur, Sprite sprite) { this.xOffset = xOffset; this.yOffset = yOffset; this.alpha = alpha; this.blur = blur; this.sprite = sprite; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { double l = alpha.getDouble(time); int b = blur.getInt(time) + 1; float a = 1.0f - (float)Math.pow(1.0 - l, 1.0/(b*b)); if (a != cachedAlpha) { cachedAlpha = a; cachedColor = new Color(0f, 0f, 0f, a); } // Set up the shadow graphics. //OverrideGraphics2D g = new OverrideGraphics2D((Graphics2D)graphics); //g.setOverrideColor(cachedColor); // Set the shadow color. Color actualColor = graphics.getColor(); graphics.setColor(cachedColor); int xo = xOffset.getInt(time) - b/2; int yo = yOffset.getInt(time) - b/2; // Draw the sprite's shadow. for (int x = 0; x < b; x++) { for (int y = 0; y < b; y++) { int xt = xo + x; int yt = yo + y; graphics.translate(xt, yt); sprite.paint(graphics, time); graphics.translate(-xt, -yt); } } // Restore the actual sprite color. graphics.setColor(actualColor); // Draw the sprite itself in the ordinary graphics. sprite.paint(graphics, time); } } ================================================ FILE: gui/src/proguard/gui/splash/SineTiming.java ================================================ /* * 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.gui.splash; /** * This Timing varies between 0 and 1, as a sine wave over time. * * @author Eric Lafortune */ public class SineTiming implements Timing { private final long period; private final long phase; /** * Creates a new SineTiming. * @param period the time period for a full cycle. * @param phase the phase of the cycle, which is added to the actual time. */ public SineTiming(long period, long phase) { this.period = period; this.phase = phase; } // Implementation for Timing. public double getTiming(long time) { // Compute the translated and scaled sine function. return 0.5 + 0.5 * Math.sin(2.0 * Math.PI * (time + phase) / period); } } ================================================ FILE: gui/src/proguard/gui/splash/SmoothTiming.java ================================================ /* * 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.gui.splash; /** * This Timing ramps up smoothly from 0 to 1 in a given time interval. * * @author Eric Lafortune */ public class SmoothTiming implements Timing { private final long fromTime; private final long toTime; /** * Creates a new SmoothTiming. * @param fromTime the time at which the timing starts ramping up from 0. * @param toTime the time at which the timing stops ramping up at 1. */ public SmoothTiming(long fromTime, long toTime) { this.fromTime = fromTime; this.toTime = toTime; } // Implementation for Timing. public double getTiming(long time) { if (time <= fromTime) { return 0.0; } if (time >= toTime) { return 1.0; } // Compute the linear interpolation. double timing = (double) (time - fromTime) / (double) (toTime - fromTime); // Smooth the interpolation at the ends. return timing * timing * (3.0 - 2.0 * timing); } } ================================================ FILE: gui/src/proguard/gui/splash/SplashPanel.java ================================================ /* * 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.gui.splash; import proguard.gui.SwingUtil; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.lang.reflect.InvocationTargetException; /** * This JPanel renders an animated Sprite. * * @author Eric Lafortune */ public class SplashPanel extends JPanel { private final MyAnimator animator = new MyAnimator(); private final MyRepainter repainter = new MyRepainter(); private final Sprite sprite; private final double sleepFactor; private long startTime = Long.MAX_VALUE; private final long stopTime; private volatile Thread animationThread; /** * Creates a new SplashPanel with the given Sprite, which will be animated * indefinitely. * @param sprite the Sprite that will be animated. * @param processorLoad the fraction of processing time to be spend on * animating the Sprite (between 0 and 1). */ public SplashPanel(Sprite sprite, double processorLoad) { this(sprite, processorLoad, (long)Integer.MAX_VALUE); } /** * Creates a new SplashPanel with the given Sprite, which will be animated * for a limited period of time. * @param sprite the Sprite that will be animated. * @param processorLoad the fraction of processing time to be spend on * animating the Sprite (between 0 and 1). * @param stopTime the number of milliseconds after which the * animation will be stopped automatically. */ public SplashPanel(Sprite sprite, double processorLoad, long stopTime) { this.sprite = sprite; this.sleepFactor = (1.0-processorLoad) / processorLoad; this.stopTime = stopTime; // Restart the animation on a mouse click. addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { SplashPanel.this.start(); } }); } /** * Starts the animation. */ public void start() { // Go to the beginning of the animation. startTime = System.currentTimeMillis(); // Make sure we have an animation thread running. if (animationThread == null) { animationThread = new Thread(animator); animationThread.start(); } } /** * Stops the animation. */ public void stop() { // Go to the end of the animation. startTime = 0L; // Let the animation thread stop itself. animationThread = null; // Repaint the SplashPanel one last time. try { SwingUtil.invokeAndWait(repainter); } catch (InterruptedException ex) { // Nothing. } catch (InvocationTargetException ex) { // Nothing. } } // Implementation for JPanel. public void paintComponent(Graphics graphics) { super.paintComponent(graphics); sprite.paint(graphics, System.currentTimeMillis() - startTime); } /** * This Runnable makes sure its SplashPanel gets repainted regularly, * depending on the targeted processor load. */ private class MyAnimator implements Runnable { public void run() { try { while (animationThread != null) { // Check if we should stop the animation. long time = System.currentTimeMillis(); if (time > startTime + stopTime) { animationThread = null; } // Do a repaint and time it. SwingUtil.invokeAndWait(repainter); // Sleep for a proportional while. long repaintTime = System.currentTimeMillis() - time; long sleepTime = (long)(sleepFactor * repaintTime); if (sleepTime < 10L) { sleepTime = 10L; } Thread.sleep(sleepTime); } } catch (InterruptedException ex) { // Nothing. } catch (InvocationTargetException ex) { // Nothing. } } } /** * This Runnable repaints its SplashPanel. */ private class MyRepainter implements Runnable { public void run() { SplashPanel.this.repaint(); } } /** * A main method for testing the splash panel. */ public static void main(String[] args) { JFrame frame = new JFrame(); frame.setTitle("Animation"); frame.setSize(800, 600); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2); Sprite sprite = new ClipSprite( new ConstantColor(Color.white), new ConstantColor(Color.lightGray), new CircleSprite(true, new LinearInt(200, 600, new SineTiming(2345L, 0L)), new LinearInt(200, 400, new SineTiming(3210L, 0L)), new ConstantInt(150)), new ColorSprite(new ConstantColor(Color.gray), new FontSprite(new ConstantFont(new Font("sansserif", Font.BOLD, 90)), new TextSprite(new ConstantString("ProGuard"), new ConstantInt(200), new ConstantInt(300))))); SplashPanel panel = new SplashPanel(sprite, 0.5); panel.setBackground(Color.white); frame.getContentPane().add(panel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); panel.start(); } } ================================================ FILE: gui/src/proguard/gui/splash/Sprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This interface describes objects that can paint themselves, possibly varying * as a function of time. * * @author Eric Lafortune */ public interface Sprite { /** * Paints the object. * * @param graphics the Graphics to paint on. * @param time the time since the start of the animation, expressed in * milliseconds. */ public void paint(Graphics graphics, long time); } ================================================ FILE: gui/src/proguard/gui/splash/TextSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This Sprite represents a text. * * @author Eric Lafortune */ public class TextSprite implements Sprite { private final VariableString[] text; private final VariableInt spacing; private final VariableInt x; private final VariableInt y; /** * Creates a new TextSprite containing a single line of text. * @param text the variable text string. * @param x the variable x-coordinate of the lower-left corner of the text. * @param y the variable y-coordinate of the lower-left corner of the text. */ public TextSprite(VariableString text, VariableInt x, VariableInt y) { this(new VariableString[] { text }, new ConstantInt(0), x, y); } /** * Creates a new TextSprite containing a multiple lines of text. * @param text the variable text strings. * @param spacing the variable spacing between the lines of text. * @param x the variable x-coordinate of the lower-left corner of the * first line of text. * @param y the variable y-coordinate of the lower-left corner of the * first line of text. */ public TextSprite(VariableString[] text, VariableInt spacing, VariableInt x, VariableInt y) { this.text = text; this.spacing = spacing; this.x = x; this.y = y; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { int xt = x.getInt(time); int yt = y.getInt(time); int spacingt = spacing.getInt(time); for (int index = 0; index < text.length; index++) { graphics.drawString(text[index].getString(time), xt, yt + index * spacingt); } } } ================================================ FILE: gui/src/proguard/gui/splash/TimeSwitchSprite.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This Sprite displays another Sprite in a given time interval. * The time of the encapsulated Sprite is shifted by the start time. * * @author Eric Lafortune */ public class TimeSwitchSprite implements Sprite { private final long onTime; private final long offTime; private final Sprite sprite; /** * Creates a new TimeSwitchSprite for displaying a given Sprite starting at * a given time. * @param onTime the start time. * @param sprite the toggled Sprite. */ public TimeSwitchSprite(long onTime, Sprite sprite) { this(onTime, 0L, sprite); } /** * Creates a new TimeSwitchSprite for displaying a given Sprite in a given * time interval. * @param onTime the start time. * @param offTime the stop time. * @param sprite the toggled Sprite. */ public TimeSwitchSprite(long onTime, long offTime, Sprite sprite) { this.onTime = onTime; this.offTime = offTime; this.sprite = sprite; } // Implementation for Sprite. public void paint(Graphics graphics, long time) { if (time >= onTime && (offTime <= 0 || time <= offTime)) { sprite.paint(graphics, time - onTime); } } } ================================================ FILE: gui/src/proguard/gui/splash/Timing.java ================================================ /* * 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.gui.splash; /** * This interface maps a time to a normalized timing between 0 and 1. * * @author Eric Lafortune */ interface Timing { /** * Returns the timing for the given time. */ public double getTiming(long time); } ================================================ FILE: gui/src/proguard/gui/splash/TypeWriterString.java ================================================ /* * 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.gui.splash; /** * This VariableString produces a String that grows linearly with respect to its * Timing, as if it is being written on a typewriter. A cursor at the end * precedes the typed characters. * * @author Eric Lafortune */ public class TypeWriterString implements VariableString { private final String string; private final Timing timing; private int cachedLength = -1; private String cachedString; /** * Creates a new TypeWriterString. * @param string the basic String. * @param timing the applied timing. */ public TypeWriterString(String string, Timing timing) { this.string = string; this.timing = timing; } // Implementation for VariableString. public String getString(long time) { double t = timing.getTiming(time); int stringLength = string.length(); int length = (int)(stringLength * t + 0.5); if (length != cachedLength) { cachedLength = length; cachedString = string.substring(0, length); if (t > 0.0 && length < stringLength) { cachedString += "_"; } } return cachedString; } } ================================================ FILE: gui/src/proguard/gui/splash/VariableColor.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This interface represents a Color that varies with time. * * @author Eric Lafortune */ interface VariableColor { /** * Returns the Color for the given time. */ public Color getColor(long time); } ================================================ FILE: gui/src/proguard/gui/splash/VariableDouble.java ================================================ /* * 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.gui.splash; /** * This interface represents a double that varies with time. * * @author Eric Lafortune */ interface VariableDouble { /** * Returns the double for the given time. */ public double getDouble(long time); } ================================================ FILE: gui/src/proguard/gui/splash/VariableFont.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This interface represents a Font that varies with time. * * @author Eric Lafortune */ interface VariableFont { /** * Returns the Font for the given time. */ public Font getFont(long time); } ================================================ FILE: gui/src/proguard/gui/splash/VariableInt.java ================================================ /* * 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.gui.splash; /** * This interface represents an integer that varies with time. * * @author Eric Lafortune */ interface VariableInt { /** * Returns the integer for the given time. */ public int getInt(long time); } ================================================ FILE: gui/src/proguard/gui/splash/VariableSizeFont.java ================================================ /* * 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.gui.splash; import java.awt.*; /** * This VariableFont varies in size with respect to its Timing. * * @author Eric Lafortune */ public class VariableSizeFont implements VariableFont { private final Font font; private final VariableDouble size; private float cachedSize = -1.0f; private Font cachedFont; /** * Creates a new VariableSizeFont * @param font the base font. * @param size the variable size of the font. */ public VariableSizeFont(Font font, VariableDouble size) { this.font = font; this.size = size; } // Implementation for VariableFont. public Font getFont(long time) { float s = (float)size.getDouble(time); if (s != cachedSize) { cachedSize = s; cachedFont = font.deriveFont((float)s); } return cachedFont; } } ================================================ FILE: gui/src/proguard/gui/splash/VariableString.java ================================================ /* * 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.gui.splash; /** * This interface represents a String that varies with time. * * @author Eric Lafortune */ interface VariableString { /** * Returns the String for the given time. */ public String getString(long time); } ================================================ FILE: gui/src/proguard/gui/splash/package.html ================================================ This package contains a library for creating splash screens and animations with text, graphical elements, and some special effects. ================================================ FILE: proguard-app/build.gradle ================================================ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { id 'com.github.johnrengelman.shadow' id 'java' id 'application' } repositories { mavenCentral() } dependencies { implementation project(':base') } jar.manifest.attributes('Implementation-Version': version) task fatJar(type: ShadowJar) { mainClassName = 'proguard.ProGuard' destinationDirectory.set(file("$rootDir/lib")) archiveFileName.set('proguard.jar') configurations = [project.configurations.runtimeClasspath] manifest { attributes( 'Main-Class': 'proguard.ProGuard', 'Multi-Release': true, 'Implementation-Version': archiveVersion.get()) } } assemble.dependsOn fatJar ================================================ FILE: retrace/build.gradle ================================================ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { id 'com.github.johnrengelman.shadow' id 'java' id 'maven-publish' } repositories { mavenCentral() } sourceSets.main { java { srcDirs = ['src'] } resources { srcDirs = ['src'] include '**/*.properties' include '**/*.gif' include '**/*.png' include '**/*.pro' } } dependencies { implementation project(':base') } task fatJar(type: ShadowJar) { destinationDirectory.set(file("$rootDir/lib")) archiveFileName.set('retrace.jar') from sourceSets.main.output configurations = [project.configurations.runtimeClasspath] manifest { attributes( 'Manifest-Version': '1.0', 'Multi-Release': true, 'Main-Class': 'proguard.retrace.ReTrace') } } assemble.dependsOn fatJar afterEvaluate { publishing { publications.getByName(project.name) { pom { description = "ReTrace is a companion tool for ProGuard and DexGuard that 'de-obfuscates' stack traces." } } } } ================================================ FILE: retrace/src/proguard/retrace/FrameInfo.java ================================================ /* * 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.retrace; /** * This class represents the class name, field name, method name, etc. * possibly found in a stack frame. Values that are not defined are null. */ public class FrameInfo { private final String className; private final String sourceFile; private final int lineNumber; private final String type; private final String fieldName; private final String methodName; private final String arguments; /** * Creates a new FrameInfo with the given information. * Any undefined values can be null. */ public FrameInfo(String className, String sourceFile, int lineNumber, String type, String fieldName, String methodName, String arguments) { this.className = className; this.sourceFile = sourceFile; this.lineNumber = lineNumber; this.type = type; this.fieldName = fieldName; this.methodName = methodName; this.arguments = arguments; } public String getClassName() { return className; } public String getSourceFile() { return sourceFile; } public int getLineNumber() { return lineNumber; } public String getType() { return type; } public String getFieldName() { return fieldName; } public String getMethodName() { return methodName; } public String getArguments() { return arguments; } // Implementations for Object. public String toString() { return FrameInfo.class.getName() + "(class=["+className+"], line=["+lineNumber+"], type=["+type+"], field=["+fieldName+"], method=["+methodName+"], arguments=["+arguments+"]"; } } ================================================ FILE: retrace/src/proguard/retrace/FramePattern.java ================================================ /* * 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.retrace; import proguard.classfile.util.ClassUtil; import java.util.regex.*; /** * This class can parse and format lines that represent stack frames * matching a given regular expression. * * @author Eric Lafortune */ public class FramePattern { // The pattern matcher has problems with \\b against some unicode // characters, so we're no longer using \\b for classes and class members. private static final String REGEX_CLASS = "(?:[^\\s\":./()]+\\.)*[^\\s\":./()]+"; private static final String REGEX_CLASS_SLASH = "(?:[^\\s\":./()]+/)*[^\\s\":./()]+"; // Source file strings can not start with a digit, otherwise we can not // distinguish them from line numbers. private static final String REGEX_SOURCE_FILE = "(?:[^:()\\d][^:()]*)?"; private static final String REGEX_LINE_NUMBER = "-?\\b\\d+\\b"; private static final String REGEX_TYPE = REGEX_CLASS + "(?:\\[\\])*"; private static final String REGEX_MEMBER = "?"; private static final String REGEX_ARGUMENTS = "(?:" + REGEX_TYPE + "(?:\\s*,\\s*" + REGEX_TYPE + ")*)?"; private final char[] expressionTypes = new char[32]; private final int expressionTypeCount; private final Pattern pattern; private final boolean verbose; /** * Creates a new FramePattern. */ public FramePattern(String regularExpression, boolean verbose) { // Construct the regular expression. StringBuffer expressionBuffer = new StringBuffer(regularExpression.length() + 32); int expressionTypeCount = 0; int index = 0; while (true) { int nextIndex = regularExpression.indexOf('%', index); if (nextIndex < 0 || nextIndex == regularExpression.length()-1 || expressionTypeCount == expressionTypes.length) { break; } // Copy a literal piece of the input line. expressionBuffer.append(regularExpression.substring(index, nextIndex)); expressionBuffer.append('('); char expressionType = regularExpression.charAt(nextIndex + 1); switch(expressionType) { case 'c': expressionBuffer.append(REGEX_CLASS); break; case 'C': expressionBuffer.append(REGEX_CLASS_SLASH); break; case 's': expressionBuffer.append(REGEX_SOURCE_FILE); break; case 'l': expressionBuffer.append(REGEX_LINE_NUMBER); break; case 't': expressionBuffer.append(REGEX_TYPE); break; case 'f': expressionBuffer.append(REGEX_MEMBER); break; case 'm': expressionBuffer.append(REGEX_MEMBER); break; case 'a': expressionBuffer.append(REGEX_ARGUMENTS); break; } expressionBuffer.append(')'); expressionTypes[expressionTypeCount++] = expressionType; index = nextIndex + 2; } // Copy the last literal piece of the input line. expressionBuffer.append(regularExpression.substring(index)); this.expressionTypeCount = expressionTypeCount; this.pattern = Pattern.compile(expressionBuffer.toString()); this.verbose = verbose; } /** * Parses all frame information from a given line. * @param line a line that represents a stack frame. * @return the parsed information, or null if the line doesn't match a * stack frame. */ public FrameInfo parse(String line) { // Try to match it against the regular expression. Matcher matcher = pattern.matcher(line); if (!matcher.matches()) { return null; } // The line matched the regular expression. String className = null; String sourceFile = null; int lineNumber = 0; String type = null; String fieldName = null; String methodName = null; String arguments = null; // Extract a class name, a line number, a type, and // arguments. for (int expressionTypeIndex = 0; expressionTypeIndex < expressionTypeCount; expressionTypeIndex++) { int startIndex = matcher.start(expressionTypeIndex + 1); if (startIndex >= 0) { String match = matcher.group(expressionTypeIndex + 1); char expressionType = expressionTypes[expressionTypeIndex]; switch (expressionType) { case 'c': className = match; break; case 'C': className = ClassUtil.externalClassName(match); break; case 's': sourceFile = match; break; case 'l': lineNumber = Integer.parseInt(match); break; case 't': type = match; break; case 'f': fieldName = match; break; case 'm': methodName = match; break; case 'a': arguments = match; break; } } } return new FrameInfo(className, sourceFile, lineNumber, type, fieldName, methodName, arguments); } /** * Formats the given frame information based on the given template line. * It is the reverse of {@link #parse(String)}, but optionally with * different frame information. * @param line a template line that represents a stack frame. * @param frameInfo information about a stack frame. * @return the formatted line, or null if the line doesn't match a * stack frame. */ public String format(String line, FrameInfo frameInfo) { // Try to match it against the regular expression. Matcher matcher = pattern.matcher(line); if (!matcher.matches()) { return null; } StringBuffer formattedBuffer = new StringBuffer(); int lineIndex = 0; for (int expressionTypeIndex = 0; expressionTypeIndex < expressionTypeCount; expressionTypeIndex++) { int startIndex = matcher.start(expressionTypeIndex + 1); if (startIndex >= 0) { int endIndex = matcher.end(expressionTypeIndex + 1); String match = matcher.group(expressionTypeIndex + 1); // Copy a literal piece of the input line. formattedBuffer.append(line.substring(lineIndex, startIndex)); // Copy a matched and translated piece of the input line. char expressionType = expressionTypes[expressionTypeIndex]; switch (expressionType) { case 'c': formattedBuffer.append(frameInfo.getClassName()); break; case 'C': formattedBuffer.append(ClassUtil.internalClassName(frameInfo.getClassName())); break; case 's': formattedBuffer.append(frameInfo.getSourceFile()); break; case 'l': // Add a colon if needed. if (formattedBuffer.charAt(formattedBuffer.length() - 1) != ':') { formattedBuffer.append(':'); } formattedBuffer.append(frameInfo.getLineNumber()); break; case 't': formattedBuffer.append(frameInfo.getType()); break; case 'f': if (verbose) { formattedBuffer.append(frameInfo.getType()).append(' '); } formattedBuffer.append(frameInfo.getFieldName()); break; case 'm': if (verbose) { formattedBuffer.append(frameInfo.getType()).append(' '); } formattedBuffer.append(frameInfo.getMethodName()); if (verbose) { formattedBuffer.append('(').append(frameInfo.getArguments()).append(')'); } break; case 'a': formattedBuffer.append(frameInfo.getArguments()); break; } // Skip the original element whose replacement value // has just been appended. lineIndex = endIndex; } } // Copy the last literal piece of the input line. formattedBuffer.append(line.substring(lineIndex)); // Return the formatted line. return formattedBuffer.toString(); } } ================================================ FILE: retrace/src/proguard/retrace/FrameRemapper.java ================================================ /* * 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.retrace; import proguard.obfuscate.MappingProcessor; import java.util.*; /** * This class accumulates mapping information and then transforms stack frames * accordingly. * * @author Eric Lafortune */ public class FrameRemapper implements MappingProcessor { // Obfuscated class name -> original class name. private final Map classMap = new HashMap(); // Original class name -> obfuscated member name -> member info set. private final Map>> classFieldMap = new HashMap>>(); private final Map>> classMethodMap = new HashMap>>(); /** * Transforms the given obfuscated frame back to one or more original frames. */ public List transform(FrameInfo obfuscatedFrame) { // First remap the class name. String originalClassName = originalClassName(obfuscatedFrame.getClassName()); if (originalClassName == null) { return null; } List originalFrames = new ArrayList(); // Create any transformed frames with remapped field names. transformFieldInfo(obfuscatedFrame, originalClassName, originalFrames); // Create any transformed frames with remapped method names. transformMethodInfo(obfuscatedFrame, originalClassName, originalFrames); if (originalFrames.isEmpty()) { String sourceFile = obfuscatedFrame.getSourceFile(); // Create a transformed frame with the remapped class name. originalFrames.add(new FrameInfo(originalClassName, sourceFile == null ? sourceFileName(originalClassName) : sourceFile.equals("Unknown Source") ? "Unknown Source" : sourceFileName(originalClassName), obfuscatedFrame.getLineNumber(), obfuscatedFrame.getType(), obfuscatedFrame.getFieldName(), obfuscatedFrame.getMethodName(), obfuscatedFrame.getArguments())); } return originalFrames; } /** * Transforms the obfuscated frame into one or more original frames, * if the frame contains information about a field that can be remapped. * @param obfuscatedFrame the obfuscated frame. * @param originalFieldFrames the list in which remapped frames can be * collected. */ private void transformFieldInfo(FrameInfo obfuscatedFrame, String originalClassName, List originalFieldFrames) { // Class name -> obfuscated field names. Map> fieldMap = classFieldMap.get(originalClassName); if (fieldMap != null) { // Obfuscated field names -> fields. String obfuscatedFieldName = obfuscatedFrame.getFieldName(); Set fieldSet = fieldMap.get(obfuscatedFieldName); if (fieldSet != null) { String obfuscatedType = obfuscatedFrame.getType(); String originalType = obfuscatedType == null ? null : originalType(obfuscatedType); // Find all matching fields. Iterator fieldInfoIterator = fieldSet.iterator(); while (fieldInfoIterator.hasNext()) { FieldInfo fieldInfo = fieldInfoIterator.next(); if (fieldInfo.matches(originalType)) { originalFieldFrames.add(new FrameInfo(fieldInfo.originalClassName, obfuscatedFrame.getSourceFile().equals("Unknown Source") ? "Unknown Source" : sourceFileName(fieldInfo.originalClassName), obfuscatedFrame.getLineNumber(), fieldInfo.originalType, fieldInfo.originalName, obfuscatedFrame.getMethodName(), obfuscatedFrame.getArguments())); } } } } } /** * Transforms the obfuscated frame into one or more original frames, * if the frame contains information about a method that can be remapped. * @param obfuscatedFrame the obfuscated frame. * @param originalMethodFrames the list in which remapped frames can be * collected. */ private void transformMethodInfo(FrameInfo obfuscatedFrame, String originalClassName, List originalMethodFrames) { // Class name -> obfuscated method names. Map> methodMap = classMethodMap.get(originalClassName); if (methodMap != null) { // Obfuscated method names -> methods. String obfuscatedMethodName = obfuscatedFrame.getMethodName(); Set methodSet = methodMap.get(obfuscatedMethodName); if (methodSet != null) { int obfuscatedLineNumber = obfuscatedFrame.getLineNumber(); String obfuscatedType = obfuscatedFrame.getType(); String originalType = obfuscatedType == null ? null : originalType(obfuscatedType); String obfuscatedArguments = obfuscatedFrame.getArguments(); String originalArguments = obfuscatedArguments == null ? null : originalArguments(obfuscatedArguments); // Find all matching methods. Iterator methodInfoIterator = methodSet.iterator(); while (methodInfoIterator.hasNext()) { MethodInfo methodInfo = methodInfoIterator.next(); if (methodInfo.matches(obfuscatedLineNumber, originalType, originalArguments)) { // Do we have a different original first line number? // We're allowing unknown values, represented as 0. int lineNumber = obfuscatedFrame.getLineNumber(); if (methodInfo.originalFirstLineNumber != methodInfo.obfuscatedFirstLineNumber) { // Do we have an original line number range and // sufficient information to shift the line number? lineNumber = methodInfo.originalLastLineNumber != 0 && methodInfo.originalLastLineNumber != methodInfo.originalFirstLineNumber && methodInfo.obfuscatedFirstLineNumber != 0 && lineNumber != 0 ? methodInfo.originalFirstLineNumber - methodInfo.obfuscatedFirstLineNumber + lineNumber : methodInfo.originalFirstLineNumber; } originalMethodFrames.add(new FrameInfo(methodInfo.originalClassName, obfuscatedFrame.getSourceFile().equals("Unknown Source") ? "Unknown Source" : sourceFileName(methodInfo.originalClassName), lineNumber, methodInfo.originalType, obfuscatedFrame.getFieldName(), methodInfo.originalName, methodInfo.originalArguments)); } } } } } /** * Returns the original argument types. */ private String originalArguments(String obfuscatedArguments) { StringBuilder originalArguments = new StringBuilder(); int startIndex = 0; while (true) { int endIndex = obfuscatedArguments.indexOf(',', startIndex); if (endIndex < 0) { break; } originalArguments.append(originalType(obfuscatedArguments.substring(startIndex, endIndex).trim())).append(','); startIndex = endIndex + 1; } originalArguments.append(originalType(obfuscatedArguments.substring(startIndex).trim())); return originalArguments.toString(); } /** * Returns the original type. */ private String originalType(String obfuscatedType) { int index = obfuscatedType.indexOf('['); return index >= 0 ? originalClassName(obfuscatedType.substring(0, index)) + obfuscatedType.substring(index) : originalClassName(obfuscatedType); } /** * Returns the original class name. */ public String originalClassName(String obfuscatedClassName) { String originalClassName = classMap.get(obfuscatedClassName); return originalClassName != null ? originalClassName : obfuscatedClassName; } /** * Returns the Java source file name that typically corresponds to the * given class name. */ private String sourceFileName(String className) { int index1 = className.lastIndexOf('.') + 1; int index2 = className.indexOf('$', index1); return (index2 > 0 ? className.substring(index1, index2) : className.substring(index1)) + ".java"; } // Implementations for MappingProcessor. public boolean processClassMapping(String className, String newClassName) { // Obfuscated class name -> original class name. classMap.put(newClassName, className); return true; } public void processFieldMapping(String className, String fieldType, String fieldName, String newClassName, String newFieldName) { // Obfuscated class name -> obfuscated field names. Map> fieldMap = classFieldMap.get(newClassName); if (fieldMap == null) { fieldMap = new HashMap>(); classFieldMap.put(newClassName, fieldMap); } // Obfuscated field name -> fields. Set fieldSet = fieldMap.get(newFieldName); if (fieldSet == null) { fieldSet = new LinkedHashSet(); fieldMap.put(newFieldName, fieldSet); } // Add the field information. fieldSet.add(new FieldInfo(className, fieldType, fieldName)); } public void processMethodMapping(String className, int firstLineNumber, int lastLineNumber, String methodReturnType, String methodName, String methodArguments, String newClassName, int newFirstLineNumber, int newLastLineNumber, String newMethodName) { // Original class name -> obfuscated method names. Map> methodMap = classMethodMap.get(newClassName); if (methodMap == null) { methodMap = new HashMap>(); classMethodMap.put(newClassName, methodMap); } // Obfuscated method name -> methods. Set methodSet = methodMap.get(newMethodName); if (methodSet == null) { methodSet = new LinkedHashSet(); methodMap.put(newMethodName, methodSet); } // Add the method information. methodSet.add(new MethodInfo(newFirstLineNumber, newLastLineNumber, className, firstLineNumber, lastLineNumber, methodReturnType, methodName, methodArguments)); } /** * Information about the original version and the obfuscated version of * a field (without the obfuscated class name or field name). */ private static class FieldInfo { private final String originalClassName; private final String originalType; private final String originalName; /** * Creates a new FieldInfo with the given properties. */ private FieldInfo(String originalClassName, String originalType, String originalName) { this.originalClassName = originalClassName; this.originalType = originalType; this.originalName = originalName; } /** * Returns whether the given type matches the original type of this field. * The given type may be a null wildcard. */ private boolean matches(String originalType) { return originalType == null || originalType.equals(this.originalType); } } /** * Information about the original version and the obfuscated version of * a method (without the obfuscated class name or method name). */ private static class MethodInfo { private final int obfuscatedFirstLineNumber; private final int obfuscatedLastLineNumber; private final String originalClassName; private final int originalFirstLineNumber; private final int originalLastLineNumber; private final String originalType; private final String originalName; private final String originalArguments; /** * Creates a new MethodInfo with the given properties. */ private MethodInfo(int obfuscatedFirstLineNumber, int obfuscatedLastLineNumber, String originalClassName, int originalFirstLineNumber, int originalLastLineNumber, String originalType, String originalName, String originalArguments) { this.obfuscatedFirstLineNumber = obfuscatedFirstLineNumber; this.obfuscatedLastLineNumber = obfuscatedLastLineNumber; this.originalType = originalType; this.originalArguments = originalArguments; this.originalClassName = originalClassName; this.originalName = originalName; this.originalFirstLineNumber = originalFirstLineNumber; this.originalLastLineNumber = originalLastLineNumber; } /** * Returns whether the given properties match the properties of this * method. The given properties may be null wildcards. */ private boolean matches(int obfuscatedLineNumber, String originalType, String originalArguments) { return // We're allowing unknown values, represented as 0. (obfuscatedLineNumber == 0 || obfuscatedLastLineNumber == 0 || (obfuscatedFirstLineNumber <= obfuscatedLineNumber && obfuscatedLineNumber <= obfuscatedLastLineNumber)) && (originalType == null || originalType.equals(this.originalType)) && (originalArguments == null || originalArguments.equals(this.originalArguments)); } } } ================================================ FILE: retrace/src/proguard/retrace/ReTrace.java ================================================ /* * 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.retrace; import proguard.obfuscate.MappingReader; import java.io.*; import java.util.*; /** * Tool for de-obfuscating stack traces of applications that were obfuscated * with ProGuard. * * @author Eric Lafortune */ public class ReTrace { private static final String USAGE = "Usage: java proguard.retrace.ReTrace [-regex ] [-allclassnames] [-verbose] []"; private static final String DEFAULT_REGEX = "Default regex: "; private static final String REGEX_OPTION = "-regex"; private static final String ALL_CLASS_NAMES_OPTION = "-allclassnames"; private static final String VERBOSE_OPTION = "-verbose"; // For example: "com.example.Foo.bar" private static final String REGULAR_EXPRESSION_CLASS_METHOD = "%c\\.%m"; // For example: // "(Foo.java:123:0) ~[0]" // "()(Foo.java:123:0)" (DGD-1732, unknown origin, possibly Sentry) // or no source line info (DGD-1732, Sentry) private static final String REGULAR_EXPRESSION_SOURCE_LINE = "(?:\\(\\))?(?:\\((?:%s)?(?::?%l)?(?::\\d+)?\\))?\\s*(?:~\\[.*\\])?"; // For example: "at o.afc.b + 45(:45)" // Might be present in recent stacktraces accessible from crashlytics. private static final String REGULAR_EXPRESSION_OPTIONAL_SOURCE_LINE_INFO = "(?:\\+\\s+[0-9]+)?"; // For example: " at com.example.Foo.bar(Foo.java:123:0) ~[0]" private static final String REGULAR_EXPRESSION_AT = ".*?\\bat\\s+" + REGULAR_EXPRESSION_CLASS_METHOD + "\\s*" + REGULAR_EXPRESSION_OPTIONAL_SOURCE_LINE_INFO + REGULAR_EXPRESSION_SOURCE_LINE; // For example: "java.lang.ClassCastException: com.example.Foo cannot be cast to com.example.Bar" // Every line can only have a single matched class, so we try to avoid // longer non-obfuscated class names. private static final String REGULAR_EXPRESSION_CAST1 = ".*?\\bjava\\.lang\\.ClassCastException: %c cannot be cast to .{5,}"; private static final String REGULAR_EXPRESSION_CAST2 = ".*?\\bjava\\.lang\\.ClassCastException: .* cannot be cast to %c"; // For example: "java.lang.NullPointerException: Attempt to read from field 'java.lang.String com.example.Foo.bar' on a null object reference" private static final String REGULAR_EXPRESSION_NULL_FIELD_READ = ".*?\\bjava\\.lang\\.NullPointerException: Attempt to read from field '%t %c\\.%f' on a null object reference"; // For example: "java.lang.NullPointerException: Attempt to write to field 'java.lang.String com.example.Foo.bar' on a null object reference" private static final String REGULAR_EXPRESSION_NULL_FIELD_WRITE = ".*?\\bjava\\.lang\\.NullPointerException: Attempt to write to field '%t %c\\.%f' on a null object reference"; // For example: "java.lang.NullPointerException: Attempt to invoke virtual method 'void com.example.Foo.bar(int,boolean)' on a null object reference" private static final String REGULAR_EXPRESSION_NULL_METHOD = ".*?\\bjava\\.lang\\.NullPointerException: Attempt to invoke (?:virtual|interface) method '%t %c\\.%m\\(%a\\)' on a null object reference"; // For example: "Something: com.example.FooException: something" private static final String REGULAR_EXPRESSION_THROW = "(?:.*?[:\"]\\s+)?%c(?::.*)?"; // For example: java.lang.NullPointerException: Cannot invoke "com.example.Foo.bar.foo(int)" because the return value of "com.example.Foo.bar.foo2()" is null private static final String REGULAR_EXPRESSION_RETURN_VALUE_NULL1 = ".*?\\bjava\\.lang\\.NullPointerException: Cannot invoke \\\".*\\\" because the return value of \\\"%c\\.%m\\(%a\\)\\\" is null"; private static final String REGULAR_EXPRESSION_RETURN_VALUE_NULL2 = ".*?\\bjava\\.lang\\.NullPointerException: Cannot invoke \\\"%c\\.%m\\(%a\\)\\\" because the return value of \\\".*\\\" is null"; //For example: Cannot invoke "java.net.ServerSocket.close()" because "com.example.Foo.bar" is null private static final String REGULAR_EXPRESSION_BECAUSE_IS_NULL = ".*?\\bbecause \\\"%c\\.%f\\\" is null"; // The overall regular expression for a line in the stack trace. public static final String REGULAR_EXPRESSION = "(?:" + REGULAR_EXPRESSION_AT + ")|" + "(?:" + REGULAR_EXPRESSION_CAST1 + ")|" + "(?:" + REGULAR_EXPRESSION_CAST2 + ")|" + "(?:" + REGULAR_EXPRESSION_NULL_FIELD_READ + ")|" + "(?:" + REGULAR_EXPRESSION_NULL_FIELD_WRITE + ")|" + "(?:" + REGULAR_EXPRESSION_NULL_METHOD + ")|" + "(?:" + REGULAR_EXPRESSION_RETURN_VALUE_NULL1 + ")|" + "(?:" + REGULAR_EXPRESSION_BECAUSE_IS_NULL + ")|" + "(?:" + REGULAR_EXPRESSION_THROW + ")"; // DIRTY FIX: // We need to call another regex because Java 16 stacktrace may have multiple methods in the same line. // For Example: java.lang.NullPointerException: Cannot invoke "dev.lone.itemsadder.Core.f.a.b.b.b.c.a(org.bukkit.Location, boolean)" because the return value of "dev.lone.itemsadder.Core.f.a.b.b.b.c.a()" is null //TODO: Make this stuff less hacky. public static final String REGULAR_EXPRESSION2 = "(?:" + REGULAR_EXPRESSION_RETURN_VALUE_NULL2 + ")"; // The settings. private final String regularExpression; private final String regularExpression2; private final boolean allClassNames; private final boolean verbose; private final File mappingFile; /** * Creates a new ReTrace instance with a default regular expression, * @param mappingFile the mapping file that was written out by * ProGuard. */ public ReTrace(File mappingFile) { this(REGULAR_EXPRESSION, REGULAR_EXPRESSION2, false, false, mappingFile); } /** * Creates a new ReTrace instance. * @param regularExpression the regular expression for parsing the lines in * the stack trace. * @param allClassNames specifies whether all words that match class * names should be de-obfuscated, even if they * aren't matching the regular expression. * @param verbose specifies whether the de-obfuscated stack trace * should be verbose. * @param mappingFile the mapping file that was written out by * ProGuard. */ public ReTrace(String regularExpression, String regularExpression2, boolean allClassNames, boolean verbose, File mappingFile) { this.regularExpression = regularExpression; this.regularExpression2 = regularExpression2; this.allClassNames = allClassNames; this.verbose = verbose; this.mappingFile = mappingFile; } /** * De-obfuscates a given stack trace. * @param stackTraceReader a reader for the obfuscated stack trace. * @param stackTraceWriter a writer for the de-obfuscated stack trace. */ public void retrace(LineNumberReader stackTraceReader, PrintWriter stackTraceWriter) throws IOException { // Create a pattern for stack frames. FramePattern pattern1 = new FramePattern(regularExpression, verbose); FramePattern pattern2 = new FramePattern(regularExpression2, verbose); // Create a remapper. FrameRemapper mapper = new FrameRemapper(); // Read the mapping file. MappingReader mappingReader = new MappingReader(mappingFile); mappingReader.pump(mapper); // Read and process the lines of the stack trace. while (true) { // Read a line. String obfuscatedLine = stackTraceReader.readLine(); if (obfuscatedLine == null) { break; } // Try to match it against the regular expression. FrameInfo obfuscatedFrame1 = pattern1.parse(obfuscatedLine); FrameInfo obfuscatedFrame2 = pattern2.parse(obfuscatedLine); String deobf = handle(obfuscatedFrame1, mapper, pattern1, obfuscatedLine); // DIRTY FIX: // I have to execute it two times because recent Java stacktraces may have multiple fields/methods in the same line. // For example: java.lang.NullPointerException: Cannot invoke "com.example.Foo.bar.foo(int)" because the return value of "com.example.Foo.bar.foo2()" is null deobf = handle(obfuscatedFrame2, mapper, pattern2, deobf); stackTraceWriter.println(deobf); } stackTraceWriter.flush(); } private String handle(FrameInfo obfuscatedFrame, FrameRemapper mapper, FramePattern pattern, String obfuscatedLine) { StringBuilder result = new StringBuilder(); if (obfuscatedFrame != null) { // Transform the obfuscated frame back to one or more // original frames. Iterator retracedFrames = mapper.transform(obfuscatedFrame).iterator(); String previousLine = null; while (retracedFrames.hasNext()) { // Retrieve the next retraced frame. FrameInfo retracedFrame = retracedFrames.next(); // Format the retraced line. String retracedLine = pattern.format(obfuscatedLine, retracedFrame); // Clear the common first part of ambiguous alternative // retraced lines, to present a cleaner list of // alternatives. String trimmedLine = previousLine != null && obfuscatedFrame.getLineNumber() == 0 ? trim(retracedLine, previousLine) : retracedLine; // Print out the retraced line. if (trimmedLine != null) { if (allClassNames) { trimmedLine = deobfuscateTokens(trimmedLine, mapper); } result.append(trimmedLine); } previousLine = retracedLine; } } else { if (allClassNames) { obfuscatedLine = deobfuscateTokens(obfuscatedLine, mapper); } // Print out the original line. result.append(obfuscatedLine); } return result.toString(); } /** * Attempts to deobfuscate each token of the line to a corresponding * original classname if possible. */ private String deobfuscateTokens(String line, FrameRemapper mapper) { StringBuilder sb = new StringBuilder(); // Try to deobfuscate any token encountered in the line. StringTokenizer st = new StringTokenizer(line, "[]{}()/\\:;, '\"<>", true); while (st.hasMoreTokens()) { sb.append(mapper.originalClassName(st.nextToken())); } return sb.toString(); } /** * Returns the first given string, with any leading characters that it has * in common with the second string replaced by spaces. */ private String trim(String string1, String string2) { StringBuilder line = new StringBuilder(string1); // Find the common part. int trimEnd = firstNonCommonIndex(string1, string2); if (trimEnd == string1.length()) { return null; } // Don't clear the last identifier characters. trimEnd = lastNonIdentifierIndex(string1, trimEnd) + 1; // Clear the common characters. for (int index = 0; index < trimEnd; index++) { if (!Character.isWhitespace(string1.charAt(index))) { line.setCharAt(index, ' '); } } return line.toString(); } /** * Returns the index of the first character that is not the same in both * given strings. */ private int firstNonCommonIndex(String string1, String string2) { int index = 0; while (index < string1.length() && index < string2.length() && string1.charAt(index) == string2.charAt(index)) { index++; } return index; } /** * Returns the index of the last character that is not an identifier * character in the given string, at or before the given index. */ private int lastNonIdentifierIndex(String line, int index) { while (index >= 0 && Character.isJavaIdentifierPart(line.charAt(index))) { index--; } return index; } /** * The main program for ReTrace. */ public static void main(String[] args) { // Parse the arguments. if (args.length < 1) { System.err.println(USAGE); System.err.println(); System.err.println(DEFAULT_REGEX + REGULAR_EXPRESSION); System.exit(-1); } String regularExpression = REGULAR_EXPRESSION; String regularExpression2 = REGULAR_EXPRESSION2; boolean verbose = false; boolean allClassNames = false; int argumentIndex = 0; while (argumentIndex < args.length) { String arg = args[argumentIndex]; if (arg.equals(REGEX_OPTION)) { regularExpression = args[++argumentIndex]; } else if (arg.equals(ALL_CLASS_NAMES_OPTION)) { allClassNames = true; } else if (arg.equals(VERBOSE_OPTION)) { verbose = true; } else { break; } argumentIndex++; } if (argumentIndex >= args.length) { System.err.println(USAGE); System.exit(-1); } // Convert the arguments into File instances. File mappingFile = new File(args[argumentIndex++]); File stackTraceFile = argumentIndex < args.length ? new File(args[argumentIndex]) : null; try { // Open the input stack trace. We're always using the UTF-8 // character encoding, even for reading from the standard // input. LineNumberReader reader = new LineNumberReader( new BufferedReader( new InputStreamReader(stackTraceFile == null ? System.in : new FileInputStream(stackTraceFile), "UTF-8"))); // Open the output stack trace, again using UTF-8 encoding. PrintWriter writer = new PrintWriter(new OutputStreamWriter(System.out, "UTF-8")); try { // Execute ReTrace with the collected settings. new ReTrace(regularExpression, regularExpression2, allClassNames, verbose, mappingFile) .retrace(reader, writer); } finally { // Close the input stack trace if it was a file. if (stackTraceFile != null) { reader.close(); } } } catch (IOException ex) { if (verbose) { // Print a verbose stack trace. ex.printStackTrace(); } else { // Print just the stack trace message. System.err.println("Error: "+ex.getMessage()); } System.exit(1); } System.exit(0); } } ================================================ FILE: retrace/src/proguard/retrace/package.html ================================================ This package contains the main ReTrace application. ReTrace can de-obfuscate stack traces of obfuscated programs. ================================================ FILE: scripts/docker/gh_action.sh ================================================ #!/usr/bin/sh # # Script for building dProtect withing the docker image: openobfuscator/dprotect-build # It requires to mount the source code at /dprotect (-v :/dprotect) # set -ex export GRADLE_OPTS="-Dorg.gradle.project.buildDir=/tmp/dprotect-build" # clone & build dprotect-core pushd /dprotect python3 ./scripts/fetch_dprotect_core.py . /core pushd /core gradle :dprotect-core:publishToMavenLocal popd popd # build dProtect pushd /dprotect gradle distZip popd # copy the zip file in /dist cp /tmp/dprotect-build/distributions/dprotect-*.zip /dprotect/dist/ ================================================ FILE: scripts/docker/publish_gradle_plugin.sh ================================================ #!/usr/bin/sh # # Script for building and publishing dProtect # It requires to mount the source code at /dprotect (-v :/dprotect) # set -ex # clone & build dprotect-core pushd /dprotect python3 ./scripts/fetch_dprotect_core.py . /core pushd /core gradle :dprotect-core:publishToMavenLocal gradle :dprotect-core:publishAllPublicationsToGithubRepository popd popd # build dProtect pushd /dprotect gradle :gradle:publishAllPublicationsToGithubRepository gradle :base:publishAllPublicationsToGithubRepository popd ================================================ FILE: scripts/fetch_dprotect_core.py ================================================ #!/usr/bin/env python """ Script used to clone the right version of dProtect-Core associated with the current dProtect repo. Usage: python ./scripts/fetch_dprotect_core.py . ~/dev/core """ import argparse import subprocess import sys import shutil import string from pathlib import Path GIT = shutil.which("git") PROP_KEYWORD = "dprotectCoreCommit" REPO = "https://github.com/open-obfuscator/dProtect-core" UPSTREAM_KEYWORD = "latest" def is_commit(value: str): if len(value) != len("f581758ec484dd99405172327e2a41833c003d33") and len(value) != len("f581758"): return False return all(x.lower() in string.hexdigits for x in value) def git_clone(dst: Path, branch: str = None, commit: str = None, shallow: bool = True, force: bool = False): if dst.is_dir() and force: shutil.rmtree(dst) command = [ shutil.which("git"), "clone", "-j8", "--single-branch" ] if commit is not None: shallow = False if branch is not None: command += [ "--branch", branch ] if shallow: command += [ "--depth", "1" ] command += [ REPO, dst.resolve().absolute().as_posix() ] subprocess.run(command, check=True) if commit is not None: command = [ GIT, "checkout", commit ] subprocess.run(command, check=True, cwd=dst) def get_core_version(dprotect_path: Path): file = dprotect_path / "gradle.properties" if not file.is_file(): print(f"{file} does not exist!") return None properties = file.read_text().splitlines() for line in properties: if not PROP_KEYWORD in line: continue _, version = line.split("=") version = version.strip().lower() version = version.replace('"', '') return version def main(): parser = argparse.ArgumentParser() parser.add_argument("src") parser.add_argument("dst") parser.add_argument("--force", "-f") args = parser.parse_args() if GIT is None: print("Git not found!") return dprotect_path = Path(args.src) if not dprotect_path.is_dir(): print(f"Error: {dprotect_path} is not a directory!") return 1 version = get_core_version(dprotect_path) if version is None: print(f"Error: Can't read dProtectCore version") return 1 print(f"Using dProtect-core version: '{version}'") # Prepare args for git_clone() branch = "main" commit = None if version.lower() != UPSTREAM_KEYWORD: if is_commit(version): branch = None commit = version else: branch = version commit = None git_clone(Path(args.dst), branch=branch, commit=commit) return 0 if __name__ == "__main__": sys.exit(main()) ================================================ FILE: settings.gradle ================================================ pluginManagement { resolutionStrategy { eachPlugin { if (requested.id.id == 'com.github.johnrengelman.shadow') { useVersion '5.2.0' } if (requested.id.id == 'io.github.gradle-nexus.publish-plugin') { useVersion '1.1.0' } } } } rootProject.name = 'dprotect' include 'base' include 'proguard-app' include 'dprotect' include 'retrace' include 'gui' include 'gradle-plugin' include 'ant' include 'annotations' project(':gradle-plugin').name = 'gradle'