diff --git a/.travis.yml b/.travis.yml index 951537c..9db0a2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ android: - tools - tools - platform-tools - - build-tools-27.0.0 + - build-tools-27.0.3 - android-27 jdk: @@ -23,6 +23,9 @@ cache: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ +before_install: + - yes | sdkmanager "platforms;android-27" + install: - ./gradlew assemble publishToMavenLocal -Pbootstrap=true --no-daemon --rerun-tasks diff --git a/build.gradle b/build.gradle index 0a362c3..0f0ae37 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,18 @@ allprojects { - ext.kotlinVersion = '1.2.21' + ext.kotlinVersion = '1.2.31' + ext.pabloVersion = '1.0.0' + + ext.asmVersion = '6.0' + ext.gripVersion = '0.6.0-beta' + ext.logbackVersion = '1.2.3' + + ext.junitVersion = '4.12' + + ext.androidToolsVersion = '3.1.1' + ext.supportVersion = '27.1.0' + + ext.runnerVersion = '1.0.1' + ext.espressoVersion = '3.0.1' buildscript { repositories { @@ -7,6 +20,11 @@ allprojects { google() jcenter() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + classpath "io.michaelrocks.pablo:pablo:$pabloVersion" + } } repositories { @@ -14,33 +32,7 @@ allprojects { google() jcenter() } -} -allprojects { - def properties = new Properties() - properties.load(new FileInputStream(new File(rootDir, "version.properties"))) - def major = properties['version.major'] as int - def minor = properties['version.minor'] as int - def patch = properties['version.patch'] as int - def suffix = properties['version.suffix']?.toString()?.trim() ?: "" - def snapshot = properties['version.snapshot'].toBoolean() - version = "$major.$minor.$patch" + (suffix.isAllWhitespace() ? '' : "-$suffix") + (snapshot ? '-SNAPSHOT' : '') group = 'io.michaelrocks' - - ext.dryRun = properties['version.dryRun'].toBoolean() - ext.publish = properties['version.publish'].toBoolean() -} - -ext { - asmVersion = '5.2' - gripVersion = '0.5.3-beta' - logbackVersion = '1.2.3' - - junitVersion = '4.12' - - androidToolsVersion = '3.0.1' - supportVersion = '27.0.2' - - runnerVersion = '1.0.1' - espressoVersion = '3.0.1' + version = '0.2.0' } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle deleted file mode 100644 index 84925d8..0000000 --- a/buildSrc/build.gradle +++ /dev/null @@ -1,7 +0,0 @@ -repositories { - maven { url "https://plugins.gradle.org/m2/" } -} - -dependencies { - compile "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6" -} diff --git a/buildSrc/src/main/groovy/io/michaelrocks/bintray/BintrayPlugin.groovy b/buildSrc/src/main/groovy/io/michaelrocks/bintray/BintrayPlugin.groovy deleted file mode 100644 index 1d82c6b..0000000 --- a/buildSrc/src/main/groovy/io/michaelrocks/bintray/BintrayPlugin.groovy +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2016 Michael Rozumyanskiy - * - * 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 io.michaelrocks.bintray - -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.logging.Logger -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.tasks.bundling.Jar - -class BintrayPlugin implements Plugin { - private Project project - private Logger logger - - @Override - void apply(final Project project) { - this.project = project - this.logger = project.logger - - project.extensions.create("maven", BintrayPluginExtension) - - project.apply plugin: 'java' - project.apply plugin: 'maven-publish' - project.apply plugin: 'com.jfrog.bintray' - - project.afterEvaluate { - configureBintrayPublishing() - } - } - - private void configureBintrayPublishing() { - final boolean hasCredentials = project.hasProperty('bintrayUser') && project.hasProperty('bintrayKey') - if (hasCredentials) { - configureBintray() - configureArtifacts() - } - } - - private void configureBintray() { - project.bintray { - user = project.property('bintrayUser') - key = project.property('bintrayKey') - - publications = ['mavenJava'] - - dryRun = project.dryRun - publish = project.publish - pkg { - repo = getRepositoryName() - name = project.maven.artifactName ?: project.rootProject.name + '-' + project.name - - version { - released = new Date() - vcsTag = "v${project.version}" - } - } - } - } - - private void configureArtifacts() { - project.task('sourcesJar', type: Jar, dependsOn: project.classes) { - from project.sourceSets.main.allSource - } - - project.task('javadocJar', type: Jar, dependsOn: project.javadoc) { - from project.javadoc.destinationDir - } - - project.artifacts { - archives project.sourcesJar, project.javadocJar - } - - project.publishing { - publications { - mavenJava(MavenPublication) { - artifactId project.bintray.pkg.name - if (project.plugins.hasPlugin('war')) { - from project.components.web - } else { - from project.components.java - } - - artifact project.sourcesJar { - classifier = 'sources' - } - artifact project.javadocJar { - classifier = 'javadoc' - } - } - } - } - } - - private String getRepositoryName() { - return project.maven.repository ?: 'maven' - } -} diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/bintray.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/bintray.properties deleted file mode 100644 index 742f4d3..0000000 --- a/buildSrc/src/main/resources/META-INF/gradle-plugins/bintray.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=io.michaelrocks.bintray.BintrayPlugin \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index 8e6e9fe..cf2cf47 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,5 +1,5 @@ apply plugin: 'java' -apply plugin: 'bintray' +apply plugin: 'io.michaelrocks.pablo' sourceCompatibility = JavaVersion.VERSION_1_6 targetCompatibility = JavaVersion.VERSION_1_6 diff --git a/core/src/main/java/io/michaelrocks/paranoid/Obfuscate.java b/core/src/main/java/io/michaelrocks/paranoid/Obfuscate.java index 60048f8..aeb7930 100644 --- a/core/src/main/java/io/michaelrocks/paranoid/Obfuscate.java +++ b/core/src/main/java/io/michaelrocks/paranoid/Obfuscate.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Michael Rozumyanskiy + * Copyright 2018 Michael Rozumyanskiy * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) +@Retention(RetentionPolicy.CLASS) @Documented public @interface Obfuscate { } diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 62b8ee5..cd8a2c6 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -1,26 +1,19 @@ -buildscript { - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - } -} - apply plugin: 'kotlin' -apply plugin: 'bintray' +apply plugin: 'io.michaelrocks.pablo' apply plugin: 'idea' -targetCompatibility = JavaVersion.VERSION_1_8 -sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_6 +sourceCompatibility = JavaVersion.VERSION_1_6 dependencies { - compile gradleApi() - compile project(':processor') - - compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlinVersion" - + compileOnly gradleApi() compileOnly "com.android.tools.build:gradle:$androidToolsVersion" compileOnly "com.android.tools.build:gradle-api:$androidToolsVersion" -} + relocate project(':processor') + + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" +} def generatedDir = new File(projectDir, "generated") def generatedJavaSourcesDir = new File(generatedDir, "main/java") @@ -43,7 +36,6 @@ task generateBuildClass { sourceSets { main { - output.dir(builtBy: tasks.generateBuildClass, generatedJavaSourcesDir) java.srcDirs += generatedJavaSourcesDir } } @@ -61,3 +53,11 @@ idea { generatedSourceDirs += generatedJavaSourcesDir } } + +shadowJar { + relocate 'io.michaelrocks.paranoid', 'io.michaelrocks.paranoid' +} + +pablo { + repackage true +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ed88a04..c44b679 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8941bfb..3f0f9be 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/processor/build.gradle b/processor/build.gradle index 885a81b..6045d0d 100644 --- a/processor/build.gradle +++ b/processor/build.gradle @@ -1,12 +1,5 @@ -buildscript { - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - } -} - -apply plugin: 'java' apply plugin: 'kotlin' -apply plugin: 'bintray' +apply plugin: 'io.michaelrocks.pablo' sourceCompatibility = JavaVersion.VERSION_1_6 targetCompatibility = JavaVersion.VERSION_1_6 @@ -14,15 +7,21 @@ targetCompatibility = JavaVersion.VERSION_1_6 dependencies { compile project(':core') compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" - - compile "org.ow2.asm:asm:$asmVersion" - compile "org.ow2.asm:asm-commons:$asmVersion" - compile "io.michaelrocks:grip:$gripVersion" compile "ch.qos.logback:logback-classic:$logbackVersion" + relocate "org.ow2.asm:asm:$asmVersion" + relocate "org.ow2.asm:asm-commons:$asmVersion" + relocate "io.michaelrocks:grip:$gripVersion" + testCompile "junit:junit:$junitVersion" } jar { destinationDir = file('build/jar') } + +shadowJar { + relocate 'io.michaelrocks.paranoid', 'io.michaelrocks.paranoid' + relocate 'io.michaelrocks.grip', 'io.michaelrocks.paranoid.grip' + relocate 'org.objectweb.asm', 'io.michaelrocks.paranoid.asm' +} diff --git a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Analyzer.kt b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Analyzer.kt index 8b39c6b..a0f8bbf 100644 --- a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Analyzer.kt +++ b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Analyzer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017 Michael Rozumyanskiy + * Copyright 2018 Michael Rozumyanskiy * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package io.michaelrocks.paranoid.processor import io.michaelrocks.grip.Grip import io.michaelrocks.grip.and -import io.michaelrocks.grip.annotatedWith import io.michaelrocks.grip.classes import io.michaelrocks.grip.fields import io.michaelrocks.grip.from @@ -26,9 +25,7 @@ import io.michaelrocks.grip.isFinal import io.michaelrocks.grip.isStatic import io.michaelrocks.grip.mirrors.FieldMirror import io.michaelrocks.grip.mirrors.Type -import io.michaelrocks.grip.mirrors.getType import io.michaelrocks.grip.withFieldInitializer -import io.michaelrocks.paranoid.Obfuscate import java.io.File class Analyzer(private val grip: Grip) { @@ -42,7 +39,8 @@ class Analyzer(private val grip: Grip) { } private fun findTypesToObfuscate(inputs: List): Set { - val query = grip select classes from inputs where annotatedWith(OBFUSCATE_TYPE) + val registry = newObfuscatedTypeRegistry(grip.classRegistry).withCache() + val query = grip select classes from inputs where registry.shouldObfuscate() return query.execute().types.toHashSet() } @@ -60,8 +58,4 @@ class Analyzer(private val grip: Grip) { val query = grip select fields from mirror where (isStatic() and isFinal() and withFieldInitializer()) return query.execute()[type].orEmpty() } - - companion object { - private val OBFUSCATE_TYPE = getType() - } } diff --git a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/CachedObfuscatedTypeRegistry.kt b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/CachedObfuscatedTypeRegistry.kt new file mode 100644 index 0000000..fd0643f --- /dev/null +++ b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/CachedObfuscatedTypeRegistry.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Michael Rozumyanskiy + * + * 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 io.michaelrocks.paranoid.processor + +import io.michaelrocks.grip.mirrors.Type + +class CachedObfuscatedTypeRegistry( + private val registry: ObfuscatedTypeRegistry +) : ObfuscatedTypeRegistry { + private val cache = mutableMapOf() + + override fun shouldObfuscate(type: Type.Object): Boolean { + return cache.getOrPut(type) { + registry.shouldObfuscate(type) + } + } +} diff --git a/buildSrc/src/main/groovy/io/michaelrocks/bintray/BintrayPluginExtension.groovy b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ObfuscatedTypeRegistry.kt similarity index 73% rename from buildSrc/src/main/groovy/io/michaelrocks/bintray/BintrayPluginExtension.groovy rename to processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ObfuscatedTypeRegistry.kt index cd18670..3d16577 100644 --- a/buildSrc/src/main/groovy/io/michaelrocks/bintray/BintrayPluginExtension.groovy +++ b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ObfuscatedTypeRegistry.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Michael Rozumyanskiy + * Copyright 2018 Michael Rozumyanskiy * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,10 @@ * limitations under the License. */ -package io.michaelrocks.bintray +package io.michaelrocks.paranoid.processor -class BintrayPluginExtension { - String repository - String artifactName +import io.michaelrocks.grip.mirrors.Type + +interface ObfuscatedTypeRegistry { + fun shouldObfuscate(type: Type.Object): Boolean } diff --git a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ObfuscatedTypeRegistryExtensions.kt b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ObfuscatedTypeRegistryExtensions.kt new file mode 100644 index 0000000..6f35b80 --- /dev/null +++ b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ObfuscatedTypeRegistryExtensions.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Michael Rozumyanskiy + * + * 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 io.michaelrocks.paranoid.processor + +import io.michaelrocks.grip.ClassRegistry +import io.michaelrocks.grip.Grip +import io.michaelrocks.grip.mirrors.Type +import io.michaelrocks.grip.mirrors.Typed +import io.michaelrocks.grip.objectType + +fun newObfuscatedTypeRegistry(classRegistry: ClassRegistry): ObfuscatedTypeRegistry { + return ObfuscatedTypeRegistryImpl(classRegistry) +} + +fun ObfuscatedTypeRegistry.withCache(): ObfuscatedTypeRegistry { + return this as? CachedObfuscatedTypeRegistry ?: CachedObfuscatedTypeRegistry(this) +} + +fun ObfuscatedTypeRegistry.shouldObfuscate(): (Grip, Typed) -> Boolean { + return objectType { _, type -> shouldObfuscate(type) } +} diff --git a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ObfuscatedTypeRegistryImpl.kt b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ObfuscatedTypeRegistryImpl.kt new file mode 100644 index 0000000..6a05f7a --- /dev/null +++ b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ObfuscatedTypeRegistryImpl.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2018 Michael Rozumyanskiy + * + * 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 io.michaelrocks.paranoid.processor + +import io.michaelrocks.grip.ClassRegistry +import io.michaelrocks.grip.mirrors.ClassMirror +import io.michaelrocks.grip.mirrors.Type +import io.michaelrocks.grip.mirrors.getObjectTypeByInternalName + +class ObfuscatedTypeRegistryImpl( + private val classRegistry: ClassRegistry +) : ObfuscatedTypeRegistry { + override fun shouldObfuscate(type: Type.Object): Boolean { + val mirror = findClassMirror(type) ?: return false + if (OBFUSCATE_TYPE in mirror.annotations) { + return true + } + + return mirror.enclosingType?.let { shouldObfuscate(it) } ?: false + } + + private fun findClassMirror(type: Type.Object): ClassMirror? { + return try { + classRegistry.getClassMirror(type) + } catch (exception: IllegalArgumentException) { + // Sometimes Kotlin generates erroneous bytecode with InnerClass attribute referencing an non-existent class. + extractOuterType(type)?.let { findClassMirror(it) } + } + } + + private fun extractOuterType(type: Type.Object): Type.Object? { + val internalName = type.internalName + val outerInternalName = internalName.substringBeforeLast('$', "") + if (outerInternalName.isEmpty()) { + return null + } + + return getObjectTypeByInternalName(outerInternalName) + } +} diff --git a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ParanoidProcessor.kt b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ParanoidProcessor.kt index 7fc8b1f..1c795c3 100644 --- a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ParanoidProcessor.kt +++ b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/ParanoidProcessor.kt @@ -40,6 +40,8 @@ class ParanoidProcessor( private val stringRegistry = StringRegistryImpl() fun process() { + dumpConfiguration() + require(inputs.size == outputs.size) { "Input collection $inputs and output collection $outputs have different sizes" } @@ -49,10 +51,21 @@ class ParanoidProcessor( val deobfuscator = createDeobfuscator() logger.info("Prepare to generate {}", deobfuscator) - Patcher(deobfuscator, stringRegistry, grip.classRegistry).copyAndPatchClasses(inputs, outputs, analysisResult) + Patcher(deobfuscator, stringRegistry, analysisResult, grip.classRegistry).copyAndPatchClasses(inputs, outputs) Generator(deobfuscator, stringRegistry).generateDeobfuscator(sourcePath, genPath, outputs + classpath, bootClasspath) } + private fun dumpConfiguration() { + logger.info("Starting ParanoidProcessor:") + logger.info(" inputs = {}", inputs) + logger.info(" outputs = {}", outputs) + logger.info(" sourcePath = {}", sourcePath) + logger.info(" genPath = {}", genPath) + logger.info(" classpath = {}", classpath) + logger.info(" bootClasspath = {}", bootClasspath) + logger.info(" projectName = {}", projectName) + } + private fun AnalysisResult.dump() { if (configurationsByType.isEmpty()) { logger.info("No classes to obfuscate") diff --git a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Patcher.kt b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Patcher.kt index 8b38d0c..384497c 100644 --- a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Patcher.kt +++ b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Patcher.kt @@ -22,23 +22,25 @@ import io.michaelrocks.grip.mirrors.getObjectTypeByInternalName import io.michaelrocks.paranoid.processor.logging.getLogger import io.michaelrocks.paranoid.processor.model.Deobfuscator import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter import java.io.File class Patcher( private val deobfuscator: Deobfuscator, private val stringRegistry: StringRegistry, + private val analysisResult: AnalysisResult, private val classRegistry: ClassRegistry ) { private val logger = getLogger() - fun copyAndPatchClasses(inputs: List, outputs: List, analysisResult: AnalysisResult) { + fun copyAndPatchClasses(inputs: List, outputs: List) { for (index in inputs.indices) { - copyAndPatchClasses(inputs[index], outputs[index], analysisResult) + copyAndPatchClasses(inputs[index], outputs[index]) } } - private fun copyAndPatchClasses(inputPath: File, outputPath: File, analysisResult: AnalysisResult) { + private fun copyAndPatchClasses(inputPath: File, outputPath: File) { logger.info("Patching...") logger.info(" Input: {}", inputPath) logger.info(" Output: {}", outputPath) @@ -47,13 +49,8 @@ class Patcher( val relativePath = sourceFile.toRelativeString(inputPath) val targetFile = File(outputPath, relativePath) if (sourceFile.isFile) { - val type = getObjectTypeFromFile(relativePath) - val configuration = analysisResult.configurationsByType[type] - if (configuration != null) { - patchClass(sourceFile, targetFile, analysisResult.configurationsByType[type]!!) - } else { - sourceFile.parentFile.mkdirs() - sourceFile.copyTo(targetFile, true) + getObjectTypeFromFile(relativePath)?.let { type -> + copyAndPatchClass(sourceFile, targetFile, type) } } else { targetFile.mkdirs() @@ -69,16 +66,37 @@ class Patcher( return null } - private fun patchClass(sourceFile: File, targetFile: File, configuration: ClassConfiguration) { + private fun copyAndPatchClass(sourceFile: File, targetFile: File, type: Type.Object) { + if (!maybePatchClass(sourceFile, targetFile, type)) { + sourceFile.parentFile.mkdirs() + sourceFile.copyTo(targetFile, true) + } + } + + private fun maybePatchClass(sourceFile: File, targetFile: File, type: Type.Object): Boolean { + val configuration = analysisResult.configurationsByType[type] + val hasObfuscateAnnotation = OBFUSCATE_TYPE in classRegistry.getClassMirror(type).annotations + if (configuration == null && !hasObfuscateAnnotation) { + return false + } + logger.debug("Patching class...") logger.debug(" Source: {}", sourceFile) logger.debug(" Target: {}", targetFile) val reader = ClassReader(sourceFile.readBytes()) val writer = StandaloneClassWriter(reader, ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES, classRegistry) - val stringLiteralsPatcher = StringLiteralsClassPatcher(deobfuscator, stringRegistry, writer) - val stringConstantsPatcher = StringConstantsClassPatcher(configuration, stringLiteralsPatcher) - reader.accept(stringConstantsPatcher, ClassReader.SKIP_FRAMES) + val patcher = + writer + .wrapIf(hasObfuscateAnnotation) { RemoveObfuscateClassPatcher(it) } + .wrapIf(configuration != null) { StringLiteralsClassPatcher(deobfuscator, stringRegistry, it) } + .wrapIf(configuration != null) { StringConstantsClassPatcher(configuration!!, it) } + reader.accept(patcher, ClassReader.SKIP_FRAMES) targetFile.parentFile.mkdirs() targetFile.writeBytes(writer.toByteArray()) + return true + } + + private inline fun ClassVisitor.wrapIf(condition: Boolean, wrapper: (ClassVisitor) -> ClassVisitor): ClassVisitor { + return if (condition) wrapper(this) else this } } diff --git a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/RemoveObfuscateClassPatcher.kt b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/RemoveObfuscateClassPatcher.kt new file mode 100644 index 0000000..cf0f1d6 --- /dev/null +++ b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/RemoveObfuscateClassPatcher.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Michael Rozumyanskiy + * + * 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 io.michaelrocks.paranoid.processor + +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.Opcodes + +class RemoveObfuscateClassPatcher( + delegate: ClassVisitor +) : ClassVisitor(Opcodes.ASM5, delegate) { + private val obfuscateDescriptor = OBFUSCATE_TYPE.descriptor + + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { + return if (obfuscateDescriptor != desc) super.visitAnnotation(desc, visible) else null + } +} diff --git a/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Types.kt b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Types.kt new file mode 100644 index 0000000..d0a5598 --- /dev/null +++ b/processor/src/main/kotlin/io/michaelrocks/paranoid/processor/Types.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2018 Michael Rozumyanskiy + * + * 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 io.michaelrocks.paranoid.processor + +import io.michaelrocks.grip.mirrors.getObjectType +import io.michaelrocks.paranoid.Obfuscate + +val OBFUSCATE_TYPE = getObjectType() diff --git a/sample/build.gradle b/sample/build.gradle index 993ad28..8dfcc08 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -2,11 +2,12 @@ buildscript { dependencies { classpath "com.android.tools.build:gradle:$androidToolsVersion" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath "io.michaelrocks:paranoid-gradle-plugin:$version" + classpath "io.michaelrocks:paranoid-gradle-plugin:$version:repack" } } apply plugin: 'com.android.application' +apply plugin: "kotlin-android" apply plugin: 'io.michaelrocks.paranoid' android { @@ -47,6 +48,7 @@ android { } dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation "com.android.support:appcompat-v7:$supportVersion" testImplementation "junit:junit:$junitVersion" diff --git a/sample/src/androidTest/java/io/michaelrocks/paranoid/sample/MainActivityTest.java b/sample/src/androidTest/java/io/michaelrocks/paranoid/sample/MainActivityTest.java index c5b0923..65d436c 100644 --- a/sample/src/androidTest/java/io/michaelrocks/paranoid/sample/MainActivityTest.java +++ b/sample/src/androidTest/java/io/michaelrocks/paranoid/sample/MainActivityTest.java @@ -42,5 +42,7 @@ public void textViewsHaveProperText() { .check(matches(withText("Q:\r\nHow does it work?"))); onView(withId(R.id.answerTextView)) .check(matches(withText("A:\r\nIt's magic! ¯\\_(ツ)_/¯"))); + onView(withId(R.id.showDialogButton)) + .check(matches(withText("Show dialog"))); } } diff --git a/sample/src/main/java/io/michaelrocks/paranoid/sample/MainActivity.java b/sample/src/main/java/io/michaelrocks/paranoid/sample/MainActivity.java deleted file mode 100644 index e89a47b..0000000 --- a/sample/src/main/java/io/michaelrocks/paranoid/sample/MainActivity.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2018 Michael Rozumyanskiy - * - * 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 io.michaelrocks.paranoid.sample; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.widget.TextView; - -import io.michaelrocks.paranoid.Obfuscate; - -@Obfuscate -public class MainActivity extends AppCompatActivity { - private static final String QUESTION = "Q:\r\n%s"; - private static final String ANSWER = "A:\r\n%s"; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main_activity); - - final TextView questionTextView = findViewById(R.id.questionTextView); - questionTextView.setText(String.format(QUESTION, "How does it work?")); - - final TextView answerTextView = findViewById(R.id.answerTextView); - answerTextView.setText(String.format(ANSWER, "It's magic! ¯\\_(ツ)_/¯")); - } -} diff --git a/sample/src/main/java/io/michaelrocks/paranoid/sample/MainActivity.kt b/sample/src/main/java/io/michaelrocks/paranoid/sample/MainActivity.kt new file mode 100644 index 0000000..4f945c0 --- /dev/null +++ b/sample/src/main/java/io/michaelrocks/paranoid/sample/MainActivity.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Michael Rozumyanskiy + * + * 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 io.michaelrocks.paranoid.sample + +import android.app.AlertDialog +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.widget.Button +import android.widget.TextView +import android.widget.Toast +import io.michaelrocks.paranoid.Obfuscate + +@Obfuscate +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.main_activity) + + val questionTextView = findViewById(R.id.questionTextView) + questionTextView.text = String.format(QUESTION, "How does it work?") + + val answerTextView = findViewById(R.id.answerTextView) + answerTextView.text = String.format(ANSWER, "It's magic! ¯\\_(ツ)_/¯") + + val showDialogButton = findViewById