diff --git a/matching/build.gradle.kts b/matching/build.gradle.kts deleted file mode 100644 index eae807a..0000000 --- a/matching/build.gradle.kts +++ /dev/null @@ -1,85 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation - -plugins { - alias(libs.plugins.kotlinMultiplatform) - alias(libs.plugins.vanniktech.mavenPublish) -} - -group = "app.revanced" - -kotlin { - @OptIn(ExperimentalAbiValidation::class) - abiValidation { - enabled = true - } - - jvm() - - sourceSets { - commonMain.dependencies { - implementation(libs.multidexlib2) - implementation(libs.smali) - implementation(project(":patcher")) - } - jvmTest.dependencies { - implementation(libs.mockk) - implementation(libs.kotlin.test) - implementation(project(":tests")) - } - } - - compilerOptions { - freeCompilerArgs.addAll( - "-Xexplicit-backing-fields", - "-Xcontext-parameters" - ) - } -} - -tasks { - named("jvmTest") { - useJUnitPlatform() - } -} - -mavenPublishing { - publishing { - repositories { - maven { - name = "githubPackages" - url = uri("https://maven.pkg.github.com/revanced/revanced-patcher") - credentials(PasswordCredentials::class) - } - } - } - - signAllPublications() - extensions.getByType().useGpgCmd() - - coordinates(group.toString(), project.name, version.toString()) - - pom { - name = "ReVanced Patcher Matching API" - description = "Matching API used by ReVanced." - inceptionYear = "2022" - url = "https://revanced.app" - licenses { - license { - name = "GNU General Public License v3.0" - url = "https://www.gnu.org/licenses/gpl-3.0.en.html" - } - } - developers { - developer { - id = "ReVanced" - name = "ReVanced" - email = "contact@revanced.app" - } - } - scm { - connection = "scm:git:git://github.com/revanced/revanced-patcher.git" - developerConnection = "scm:git:git@github.com:revanced/revanced-patcher.git" - url = "https://github.com/revanced/revanced-patcher" - } - } -} \ No newline at end of file diff --git a/patcher/build.gradle.kts b/patcher/build.gradle.kts index f61f566..3d27d69 100644 --- a/patcher/build.gradle.kts +++ b/patcher/build.gradle.kts @@ -50,12 +50,14 @@ kotlin { jvmTest.dependencies { implementation(libs.mockk) implementation(libs.kotlin.test) - implementation(project(":tests")) } } compilerOptions { - freeCompilerArgs = listOf("-Xcontext-parameters") + freeCompilerArgs.addAll( + "-Xexplicit-backing-fields", + "-Xcontext-parameters" + ) } } diff --git a/patcher/src/commonMain/kotlin/app/revanced/patcher/Fingerprint.kt b/patcher/src/commonMain/kotlin/app/revanced/patcher/Fingerprint.kt index ded0942..4ff54a4 100644 --- a/patcher/src/commonMain/kotlin/app/revanced/patcher/Fingerprint.kt +++ b/patcher/src/commonMain/kotlin/app/revanced/patcher/Fingerprint.kt @@ -25,14 +25,14 @@ class Fingerprint internal constructor( ) { @Suppress("ktlint:standard:backing-property-naming") // Backing field needed for lazy initialization. - private var _matchOrNull: Match? = null + private var _matchOrNull: FingerprintMatch? = null context(_: BytecodePatchContext) - private val matchOrNull: Match? + private val matchOrNull: FingerprintMatch? get() = matchOrNull() context(context: BytecodePatchContext) - internal fun matchOrNull(): Match? { + internal fun matchOrNull(): FingerprintMatch? { if (_matchOrNull != null) return _matchOrNull var match = strings?.mapNotNull { @@ -57,7 +57,7 @@ class Fingerprint internal constructor( context(_: BytecodePatchContext) fun matchOrNull( classDef: ClassDef, - ): Match? { + ): FingerprintMatch? { if (_matchOrNull != null) return _matchOrNull for (method in classDef.methods) { @@ -77,7 +77,7 @@ class Fingerprint internal constructor( fun matchOrNull( method: Method, classDef: ClassDef, - ): Match? { + ): FingerprintMatch? { if (_matchOrNull != null) return _matchOrNull if (returnType != null && !method.returnType.startsWith(returnType)) { @@ -109,7 +109,7 @@ class Fingerprint internal constructor( return null } - val stringMatches: List? = + val stringMatches: List? = if (strings != null) { buildList { val instructions = method.instructionsOrNull ?: return null @@ -128,7 +128,7 @@ class Fingerprint internal constructor( val index = stringsList.indexOfFirst(string::contains) if (index == -1) return@forEachIndexed - add(Match.StringMatch(string, instructionIndex)) + add(FingerprintMatch.StringMatch(string, instructionIndex)) stringsList.removeAt(index) } @@ -141,7 +141,7 @@ class Fingerprint internal constructor( val patternMatch = if (opcodes != null) { val instructions = method.instructionsOrNull ?: return null - fun patternScan(): Match.PatternMatch? { + fun patternScan(): FingerprintMatch.PatternMatch? { val fingerprintFuzzyPatternScanThreshold = fuzzyPatternScanThreshold val instructionLength = instructions.count() @@ -168,7 +168,7 @@ class Fingerprint internal constructor( } // The entire pattern has been scanned. - return Match.PatternMatch( + return FingerprintMatch.PatternMatch( index, index + patternIndex, ) @@ -183,7 +183,7 @@ class Fingerprint internal constructor( null } - _matchOrNull = Match( + _matchOrNull = FingerprintMatch( context, classDef, method, @@ -266,7 +266,7 @@ class Fingerprint internal constructor( } @Deprecated("Use the matcher API instead.") -class Match internal constructor( +class FingerprintMatch internal constructor( val context: BytecodePatchContext, val originalClassDef: ClassDef, val originalMethod: Method, diff --git a/matching/src/commonMain/kotlin/app/revanced/patcher/Matching.kt b/patcher/src/commonMain/kotlin/app/revanced/patcher/Matching.kt similarity index 90% rename from matching/src/commonMain/kotlin/app/revanced/patcher/Matching.kt rename to patcher/src/commonMain/kotlin/app/revanced/patcher/Matching.kt index 1dc5b53..769c484 100644 --- a/matching/src/commonMain/kotlin/app/revanced/patcher/Matching.kt +++ b/patcher/src/commonMain/kotlin/app/revanced/patcher/Matching.kt @@ -97,12 +97,12 @@ fun BytecodePatchContext.firstMethodOrNull( fun BytecodePatchContext.firstMethod( vararg strings: String, predicate: MethodPredicate = { true }, -) = requireNotNull(firstMethodOrNull(*strings, predicate = predicate)) +) = requireNotNull(firstMethodOrNull(strings = strings, predicate)) fun BytecodePatchContext.firstMethodMutableOrNull( vararg strings: String, predicate: MethodPredicate = { true }, -) = firstMethodOrNull(*strings, predicate = predicate)?.let { method -> +) = firstMethodOrNull(strings = strings, predicate)?.let { method -> firstClassDefMutable(method.definingClass).methods.first { MethodUtil.methodSignaturesMatch(method, it) } @@ -110,7 +110,7 @@ fun BytecodePatchContext.firstMethodMutableOrNull( fun BytecodePatchContext.firstMethodMutable( vararg strings: String, predicate: MethodPredicate = { true } -) = requireNotNull(firstMethodMutableOrNull(*strings, predicate = predicate)) +) = requireNotNull(firstMethodMutableOrNull(strings = strings, predicate)) fun gettingFirstClassDefOrNull( type: String? = null, predicate: ClassDefPredicate = { true } @@ -131,22 +131,22 @@ fun gettingFirstClassDefMutable( fun gettingFirstMethodOrNull( vararg strings: String, predicate: MethodPredicate = { true }, -) = cachedReadOnlyProperty { firstMethodOrNull(*strings, predicate = predicate) } +) = cachedReadOnlyProperty { firstMethodOrNull(strings = strings, predicate) } fun gettingFirstMethod( vararg strings: String, predicate: MethodPredicate = { true }, -) = cachedReadOnlyProperty { firstMethod(*strings, predicate = predicate) } +) = cachedReadOnlyProperty { firstMethod(strings = strings, predicate) } fun gettingFirstMethodMutableOrNull( vararg strings: String, predicate: MethodPredicate = { true }, -) = cachedReadOnlyProperty { firstMethodMutableOrNull(*strings, predicate = predicate) } +) = cachedReadOnlyProperty { firstMethodMutableOrNull(strings = strings, predicate) } fun gettingFirstMethodMutable( vararg strings: String, predicate: MethodPredicate = { true }, -) = cachedReadOnlyProperty { firstMethodMutable(*strings, predicate = predicate) } +) = cachedReadOnlyProperty { firstMethodMutable(strings = strings, predicate) } class PredicateContext internal constructor() : MutableMap by mutableMapOf() @@ -373,22 +373,22 @@ fun BytecodePatchContext.firstClassDefMutableByDeclarativePredicate( fun BytecodePatchContext.firstMethodByDeclarativePredicateOrNull( vararg strings: String, predicate: DeclarativeMethodPredicate = { } -) = firstMethodOrNull(*strings) { rememberedDeclarativePredicate(predicate) } +) = firstMethodOrNull(strings = strings) { rememberedDeclarativePredicate(predicate) } fun BytecodePatchContext.firstMethodByDeclarativePredicate( vararg strings: String, predicate: DeclarativeMethodPredicate = { } -) = requireNotNull(firstMethodByDeclarativePredicateOrNull(*strings, predicate = predicate)) +) = requireNotNull(firstMethodByDeclarativePredicateOrNull(strings = strings, predicate)) fun BytecodePatchContext.firstMethodMutableByDeclarativePredicateOrNull( vararg strings: String, predicate: DeclarativeMethodPredicate = { } -) = firstMethodMutableOrNull(*strings) { rememberedDeclarativePredicate(predicate) } +) = firstMethodMutableOrNull(strings = strings) { rememberedDeclarativePredicate(predicate) } fun BytecodePatchContext.firstMethodMutableByDeclarativePredicate( vararg strings: String, predicate: DeclarativeMethodPredicate = { } -) = requireNotNull(firstMethodMutableByDeclarativePredicateOrNull(*strings, predicate = predicate)) +) = requireNotNull(firstMethodMutableByDeclarativePredicateOrNull(strings = strings, predicate)) fun gettingFirstClassDefByDeclarativePredicateOrNull( type: String? = null, @@ -413,22 +413,22 @@ fun gettingFirstClassDefMutableByDeclarativePredicate( fun gettingFirstMethodByDeclarativePredicateOrNull( vararg strings: String, predicate: DeclarativeMethodPredicate = { } -) = gettingFirstMethodOrNull(*strings) { rememberedDeclarativePredicate(predicate) } +) = gettingFirstMethodOrNull(strings = strings) { rememberedDeclarativePredicate(predicate) } fun gettingFirstMethodByDeclarativePredicate( vararg strings: String, predicate: DeclarativeMethodPredicate = { } -) = cachedReadOnlyProperty { firstMethodByDeclarativePredicate(*strings, predicate = predicate) } +) = cachedReadOnlyProperty { firstMethodByDeclarativePredicate(strings = strings, predicate) } fun gettingFirstMethodMutableByDeclarativePredicateOrNull( vararg strings: String, predicate: DeclarativeMethodPredicate = { } -) = gettingFirstMethodMutableOrNull(*strings) { rememberedDeclarativePredicate(predicate) } +) = gettingFirstMethodMutableOrNull(strings = strings) { rememberedDeclarativePredicate(predicate) } fun gettingFirstMethodMutableByDeclarativePredicate( vararg strings: String, predicate: DeclarativeMethodPredicate = { } -) = cachedReadOnlyProperty { firstMethodMutableByDeclarativePredicate(*strings, predicate = predicate) } +) = cachedReadOnlyProperty { firstMethodMutableByDeclarativePredicate(strings = strings, predicate) } context(list: MutableList Boolean>) fun allOf(block: MutableList Boolean>.() -> Unit) { @@ -453,21 +453,9 @@ fun all(target: T): Boolean = list.all { target.it() } context(list: MutableList Boolean>) fun any(target: T): Boolean = list.any { target.it() } -fun firstMethodBuilder( - vararg strings: String, - builder: - context(PredicateContext, MutableList Boolean>, IndexedMatcher, MutableList) () -> Unit -) = with(mutableListOf()) stringsList@{ - addAll(strings) - - with(indexedMatcher()) { - Match(indices = indices, strings = this@stringsList) { builder() } - } -} - context(_: MutableList Boolean>) fun accessFlags(vararg flags: AccessFlags) = - predicate { accessFlags(*flags) } + predicate { accessFlags(flags = flags) } context(_: MutableList Boolean>) fun returnType( @@ -624,22 +612,39 @@ fun noneOf( predicates.none { predicate -> predicate(currentIndex, lastMatchedIndex) } } -class Match internal constructor( - val indices: List, - val strings: List, - private val predicate: DeclarativeMethodPredicate +fun firstMethodBuilder( + vararg strings: String, + builder: + context(PredicateContext, MutableList Boolean>, IndexedMatcher, MutableList)() -> Unit +) = Match(strings = strings, builder) + +class Match private constructor( + private val strings: MutableList, + indexedMatcher: IndexedMatcher = indexedMatcher(), + build: context( + PredicateContext, MutableList Boolean>, + IndexedMatcher, MutableList) () -> Unit ) { - private var _methodOrNull: MutableMethod? = null + internal constructor( + vararg strings: String, + builder: context( + PredicateContext, MutableList Boolean>, + IndexedMatcher, MutableList) () -> Unit + ) : this(strings = mutableListOf(elements = strings), build = builder) + + private val methodOrNullMap = HashMap(1) + + private val predicate: DeclarativeMethodPredicate = context(strings, indexedMatcher) { { build() } } context(context: BytecodePatchContext) - val methodOrNull: MutableMethod? - get() = _methodOrNull ?: run { - _methodOrNull = if (strings.isEmpty()) - context.firstMethodMutableByDeclarativePredicateOrNull(predicate = predicate) - else - context.firstMethodMutableByDeclarativePredicateOrNull(strings = strings.toTypedArray(), predicate) - _methodOrNull + val methodOrNull: MutableMethod? + get() = if (context in methodOrNullMap) methodOrNullMap[context] + else methodOrNullMap.getOrPut(context) { + context.firstMethodMutableByDeclarativePredicateOrNull( + strings = strings.toTypedArray(), + predicate + ) } context(_: BytecodePatchContext) @@ -650,4 +655,6 @@ class Match internal constructor( context(_: BytecodePatchContext) val classDef get() = requireNotNull(classDefOrNull) + + val indices = indexedMatcher.indices } diff --git a/patcher/src/commonMain/kotlin/app/revanced/patcher/patch/Patch.kt b/patcher/src/commonMain/kotlin/app/revanced/patcher/patch/Patch.kt index f5a87a0..987bbc1 100644 --- a/patcher/src/commonMain/kotlin/app/revanced/patcher/patch/Patch.kt +++ b/patcher/src/commonMain/kotlin/app/revanced/patcher/patch/Patch.kt @@ -236,7 +236,7 @@ by patchesByFile.values.flatten().toSet() @Suppress("MISSING_DEPENDENCY_IN_INFERRED_TYPE_ANNOTATION_WARNING") internal fun getPatches(classNames: List, classLoader: ClassLoader): Set { fun Member.isUsable() = - Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && (this !is Method || parameterCount != 0) + Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && (this !is Method || parameterCount == 0) fun Class<*>.getPatchFields() = fields .filter { it.type.isPatch && it.isUsable() } diff --git a/patcher/src/jvmTest/kotlin/app/revanced/patcher/FingerprintTest.kt b/patcher/src/jvmTest/kotlin/app/revanced/patcher/FingerprintTest.kt index 48b3ab1..f97c082 100644 --- a/patcher/src/jvmTest/kotlin/app/revanced/patcher/FingerprintTest.kt +++ b/patcher/src/jvmTest/kotlin/app/revanced/patcher/FingerprintTest.kt @@ -19,7 +19,7 @@ internal class FingerprintTest : PatcherTestBase() { "Fingerprints should match correctly." ) assertNull( - fingerprint { returns("doesnt exist") }.originalMethodOrNull, + fingerprint { returns("does not exist") }.originalMethodOrNull, "Fingerprints should match correctly." ) } diff --git a/matching/src/commonTest/kotlin/app/revanced/patcher/MatchingTest.kt b/patcher/src/jvmTest/kotlin/app/revanced/patcher/MatchingTest.kt similarity index 97% rename from matching/src/commonTest/kotlin/app/revanced/patcher/MatchingTest.kt rename to patcher/src/jvmTest/kotlin/app/revanced/patcher/MatchingTest.kt index 872d6f1..fd2df01 100644 --- a/matching/src/commonTest/kotlin/app/revanced/patcher/MatchingTest.kt +++ b/patcher/src/jvmTest/kotlin/app/revanced/patcher/MatchingTest.kt @@ -18,7 +18,7 @@ object MatchingTest : PatcherTestBase() { fun setup() = setupMock() @Test - fun `matches via builder api`() { + fun `finds via builder api`() { fun firstMethodBuilder(fail: Boolean = false) = firstMethodBuilder { name("method") definingClass("class") @@ -45,7 +45,7 @@ object MatchingTest : PatcherTestBase() { } @Test - fun `matches via declarative api`() { + fun `finds via declarative api`() { bytecodePatch { apply { val method = firstMethodByDeclarativePredicateOrNull { @@ -64,7 +64,7 @@ object MatchingTest : PatcherTestBase() { } @Test - fun `predicate matcher works correctly`() { + fun `predicate api works correctly`() { bytecodePatch { apply { assertDoesNotThrow("Should find method") { firstMethod { name == "method" } } diff --git a/patcher/src/jvmTest/kotlin/app/revanced/patcher/PatcherTest.kt b/patcher/src/jvmTest/kotlin/app/revanced/patcher/PatcherTest.kt index 508f370..a68712d 100644 --- a/patcher/src/jvmTest/kotlin/app/revanced/patcher/PatcherTest.kt +++ b/patcher/src/jvmTest/kotlin/app/revanced/patcher/PatcherTest.kt @@ -77,15 +77,4 @@ internal class PatcherTest : PatcherTestBase() { "afterDependents of a patch should be called " + "regardless of dependant patches failing." } - - @Test - fun `throws if unmatched fingerprint match is used`() { - with(bytecodePatchContext) { - val fingerprint = fingerprint { strings("doesnt exist") } - - assertThrows("Expected an exception because the fingerprint can't match.") { - fingerprint.patternMatch - } - } - } } diff --git a/tests/src/commonMain/kotlin/app/revanced/patcher/PatcherTestBase.kt b/patcher/src/jvmTest/kotlin/app/revanced/patcher/PatcherTestBase.kt similarity index 88% rename from tests/src/commonMain/kotlin/app/revanced/patcher/PatcherTestBase.kt rename to patcher/src/jvmTest/kotlin/app/revanced/patcher/PatcherTestBase.kt index bc818d9..a157d6b 100644 --- a/tests/src/commonMain/kotlin/app/revanced/patcher/PatcherTestBase.kt +++ b/patcher/src/jvmTest/kotlin/app/revanced/patcher/PatcherTestBase.kt @@ -78,7 +78,9 @@ abstract class PatcherTestBase { every { this@bytecodePatchContext.getProperty("apkFile") } returns mockk() every { this@bytecodePatchContext.classDefs } returns ClassDefs().apply { - invokePrivateMethod($$"initializeCache$patcher") + javaClass.getDeclaredMethod($$"initializeCache$patcher").apply { + isAccessible = true + }.invoke(this) } every { get() } returns emptySet() @@ -100,16 +102,4 @@ abstract class PatcherTestBase { } protected operator fun Patch.invoke() = setOf(this)() - - private fun Any.setPrivateField(field: String, value: Any) { - this::class.java.getDeclaredField(field).apply { - this.isAccessible = true - set(this@setPrivateField, value) - } - } - - private fun Any.invokePrivateMethod(method: String) = - javaClass.getDeclaredMethod(method).apply { - isAccessible = true - }.invoke(this) } \ No newline at end of file diff --git a/patcher/src/jvmTest/kotlin/app/revanced/patcher/patch/OptionsTest.kt b/patcher/src/jvmTest/kotlin/app/revanced/patcher/patch/OptionsTest.kt index 6a85aba..94bef2d 100644 --- a/patcher/src/jvmTest/kotlin/app/revanced/patcher/patch/OptionsTest.kt +++ b/patcher/src/jvmTest/kotlin/app/revanced/patcher/patch/OptionsTest.kt @@ -1,6 +1,5 @@ package app.revanced.patcher.patch -import app.revanced.patcher.patch.PatchBuilder.invoke import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import kotlin.reflect.typeOf diff --git a/patcher/src/jvmTest/kotlin/app/revanced/patcher/util/SmaliTest.kt b/patcher/src/jvmTest/kotlin/app/revanced/patcher/util/SmaliTest.kt index a5c2d0c..ffd2a86 100644 --- a/patcher/src/jvmTest/kotlin/app/revanced/patcher/util/SmaliTest.kt +++ b/patcher/src/jvmTest/kotlin/app/revanced/patcher/util/SmaliTest.kt @@ -41,7 +41,7 @@ internal class SmaliTest { """, ) - val targetLocationIndex = method.getInstruction(0).target.location.index + val targetLocationIndex = method.getInstruction(1).target.location.index assertEquals(0, targetLocationIndex, "Label should point to index 0") } diff --git a/settings.gradle.kts b/settings.gradle.kts index 289f08b..c7d3af2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,5 +22,3 @@ dependencyResolutionManagement { } include(":patcher") -include(":matching") -include(":tests") diff --git a/tests/build.gradle.kts b/tests/build.gradle.kts deleted file mode 100644 index 4d60e85..0000000 --- a/tests/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -import com.android.build.api.dsl.androidLibrary -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation - -plugins { - alias(libs.plugins.kotlinMultiplatform) -} - -group = "app.revanced" - -kotlin { - jvm() - - sourceSets { - commonMain.dependencies { - implementation(libs.kotlinx.coroutines.core) - implementation(libs.multidexlib2) - implementation(libs.smali) - implementation(project(":patcher")) - implementation(libs.mockk) - implementation(libs.kotlin.test) - } - } -}