From f9b69f9bee33aa101cdf4211e9a6f43ed9579a85 Mon Sep 17 00:00:00 2001 From: Hadi Satrio Date: Thu, 2 May 2024 15:50:27 +0700 Subject: [PATCH 1/3] Pull out ZipFile provider from ApkLibraryInstaller --- .../getkeepsafe/relinker/ActualZipFileFactory.java | 12 ++++++++++++ .../getkeepsafe/relinker/ApkLibraryInstaller.java | 14 ++++++++++++-- .../com/getkeepsafe/relinker/ZipFileFactory.java | 9 +++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 relinker/src/main/java/com/getkeepsafe/relinker/ActualZipFileFactory.java create mode 100644 relinker/src/main/java/com/getkeepsafe/relinker/ZipFileFactory.java diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/ActualZipFileFactory.java b/relinker/src/main/java/com/getkeepsafe/relinker/ActualZipFileFactory.java new file mode 100644 index 0000000..5146360 --- /dev/null +++ b/relinker/src/main/java/com/getkeepsafe/relinker/ActualZipFileFactory.java @@ -0,0 +1,12 @@ +package com.getkeepsafe.relinker; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipFile; + +public class ActualZipFileFactory implements ZipFileFactory { + @Override + public ZipFile create(File file, int openMode) throws IOException { + return new ZipFile(file, openMode); + } +} diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java b/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java index 396f849..5d9f7f0 100644 --- a/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java +++ b/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java @@ -39,6 +39,16 @@ public class ApkLibraryInstaller implements ReLinker.LibraryInstaller { private static final int MAX_TRIES = 5; private static final int COPY_BUFFER_SIZE = 4096; + private final ZipFileFactory zipFileFactory; + + ApkLibraryInstaller() { + this(new ActualZipFileFactory()); + } + + ApkLibraryInstaller(ZipFileFactory zipFileFactory) { + this.zipFileFactory = zipFileFactory; + } + private String[] sourceDirectories(final Context context) { final ApplicationInfo appInfo = context.getApplicationInfo(); @@ -74,7 +84,7 @@ private ZipFileInZipEntry findAPKWithLibrary(final Context context, int tries = 0; while (tries++ < MAX_TRIES) { try { - zipFile = new ZipFile(new File(sourceDir), ZipFile.OPEN_READ); + zipFile = zipFileFactory.create(new File(sourceDir), ZipFile.OPEN_READ); break; } catch (IOException ignored) { } @@ -123,7 +133,7 @@ private String[] getSupportedABIs(Context context, String mappedLibraryName) { Set supportedABIs = new HashSet(); for (String sourceDir : sourceDirectories(context)) { try { - zipFile = new ZipFile(new File(sourceDir), ZipFile.OPEN_READ); + zipFile = zipFileFactory.create(new File(sourceDir), ZipFile.OPEN_READ); } catch (IOException ignored) { continue; } diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/ZipFileFactory.java b/relinker/src/main/java/com/getkeepsafe/relinker/ZipFileFactory.java new file mode 100644 index 0000000..4542451 --- /dev/null +++ b/relinker/src/main/java/com/getkeepsafe/relinker/ZipFileFactory.java @@ -0,0 +1,9 @@ +package com.getkeepsafe.relinker; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipFile; + +public interface ZipFileFactory { + ZipFile create(File file, int openMode) throws IOException; +} From f4f193869d55a06df4b2376987082eee10c65f9a Mon Sep 17 00:00:00 2001 From: Hadi Satrio Date: Thu, 2 May 2024 16:21:07 +0700 Subject: [PATCH 2/3] Include report of unscannable files in MissingLibraryException --- .../getkeepsafe/relinker/AbiSupportInfo.java | 34 +++++++++++++++++++ .../relinker/ApkLibraryInstaller.java | 16 +++++---- .../relinker/MissingLibraryException.java | 27 ++++++++++++--- .../relinker/ApkLibraryInstallerTest.java | 19 +++++++++++ .../relinker/FaultyZipFileFactory.java | 12 +++++++ 5 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java create mode 100644 relinker/src/test/java/com/getkeepsafe/relinker/FaultyZipFileFactory.java diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java b/relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java new file mode 100644 index 0000000..6bb11d6 --- /dev/null +++ b/relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java @@ -0,0 +1,34 @@ +package com.getkeepsafe.relinker; + +import java.io.File; +import java.util.Collections; +import java.util.Set; + +public class AbiSupportInfo { + private final Set supportedAbis; + private final Set unScannableFiles; + + public AbiSupportInfo(Set supportedAbis) { + this(supportedAbis, Collections.emptySet()); + } + + public AbiSupportInfo(Set supportedAbis, Set unscannableFiles) { + this.supportedAbis = supportedAbis; + this.unScannableFiles = unscannableFiles; + } + + public String[] getSupportedAbis() { + String[] arr = new String[supportedAbis.size()]; + supportedAbis.toArray(arr); + return arr; + } + + public String[] getUnscannableFileNames() { + String[] arr = new String[unScannableFiles.size()]; + int i = 0; + for (File file : unScannableFiles) { + arr[i++] = file.getName(); + } + return arr; + } +} diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java b/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java index 5d9f7f0..5852c37 100644 --- a/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java +++ b/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; @@ -126,15 +127,18 @@ private ZipFileInZipEntry findAPKWithLibrary(final Context context, // This second loop is more expensive than trying to find a specific ABI, so it should // only be ran when no matching libraries are found. This should keep the overhead of // the happy path to a minimum. - private String[] getSupportedABIs(Context context, String mappedLibraryName) { + private AbiSupportInfo getSupportedABIs(Context context, String mappedLibraryName) { String p = "lib" + File.separatorChar + "([^\\" + File.separatorChar + "]*)" + File.separatorChar + mappedLibraryName; Pattern pattern = Pattern.compile(p); ZipFile zipFile; Set supportedABIs = new HashSet(); + Set unscannableFiles = new HashSet(); for (String sourceDir : sourceDirectories(context)) { + File source = new File(sourceDir); try { - zipFile = zipFileFactory.create(new File(sourceDir), ZipFile.OPEN_READ); + zipFile = zipFileFactory.create(source, ZipFile.OPEN_READ); } catch (IOException ignored) { + unscannableFiles.add(source); continue; } @@ -148,8 +152,7 @@ private String[] getSupportedABIs(Context context, String mappedLibraryName) { } } - String[] result = new String[supportedABIs.size()]; - return supportedABIs.toArray(result); + return new AbiSupportInfo(supportedABIs, unscannableFiles); } /** @@ -173,14 +176,13 @@ public void installLibrary(final Context context, if (found == null) { // Does not exist in any APK. Report exactly what ReLinker is looking for and // what is actually supported by the APK. - String[] supportedABIs; + AbiSupportInfo supportedABIs; try { supportedABIs = getSupportedABIs(context, mappedLibraryName); } catch (Exception e) { // Should never happen as this indicates a bug in ReLinker code, but just to be safe. // User code should only ever crash with a MissingLibraryException if getting this far. - supportedABIs = new String[1]; - supportedABIs[0] = e.toString(); + supportedABIs = new AbiSupportInfo(Collections.singleton(e.toString())); } throw new MissingLibraryException(mappedLibraryName, abis, supportedABIs); } diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java b/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java index 739d8d1..8ae6342 100755 --- a/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java +++ b/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java @@ -18,9 +18,28 @@ import java.util.Arrays; public class MissingLibraryException extends RuntimeException { - public MissingLibraryException(final String library, final String[] wantedABIs, final String[] supportedABIs) { - super("Could not find '" + library + "'. " + - "Looked for: " + Arrays.toString(wantedABIs) + ", " + - "but only found: " + Arrays.toString(supportedABIs) + "."); + private final String library; + private final String[] wantedABIs; + private final AbiSupportInfo abiSupportInfo; + + public MissingLibraryException(String library, String[] wantedABIs, AbiSupportInfo abiSupportInfo) { + this.library = library; + this.wantedABIs = wantedABIs; + this.abiSupportInfo = abiSupportInfo; + } + + @Override + public String getMessage() { + StringBuilder sb = new StringBuilder(); + sb.append("Could not find '").append(library).append("'. "); + sb.append("Looked for: ").append(Arrays.toString(wantedABIs)).append(", "); + sb.append("but only found: ").append(Arrays.toString(abiSupportInfo.getSupportedAbis())).append("."); + + String[] unscannableFileNames = abiSupportInfo.getUnscannableFileNames(); + if (unscannableFileNames.length != 0) { + sb.append(" Additionally, encountered errors while scanning: ").append(Arrays.toString(unscannableFileNames)).append("."); + } + + return sb.toString(); } } diff --git a/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java b/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java index 0869b41..be5d972 100644 --- a/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java +++ b/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java @@ -80,6 +80,25 @@ public void throwsMissingLibraryExceptionWhenABIIsMissing() throws IOException { } } + @Test + public void throwsMissingLibraryExceptionWhenABIIsMissingAndThereWereUnscannableDirs() throws IOException { + final Context context = mock(Context.class); + final ApplicationInfo appInfo = mock(ApplicationInfo.class); + final ReLinkerInstance instance = mock(ReLinkerInstance.class); + final ApkLibraryInstaller installer = new ApkLibraryInstaller(new FaultyZipFileFactory()); + final File destination = tempFolder.newFile("test"); + final String[] abis = new String[] {"armeabi-v7a"}; // For unit test running on a developer machine this is normally x86 + + when(context.getApplicationInfo()).thenReturn(appInfo); + appInfo.sourceDir = getClass().getResource("/fake.apk").getFile(); + + try { + installer.installLibrary(context, abis, "libtest.so", destination, instance); + } catch (MissingLibraryException e) { + assertEquals("Could not find 'libtest.so'. Looked for: [armeabi-v7a], but only found: []. Additionally, encountered errors while scanning: [fake.apk].", e.getMessage()); + } + } + private String fileToString(final File file) throws IOException { final long size = file.length(); if (size > Integer.MAX_VALUE) { diff --git a/relinker/src/test/java/com/getkeepsafe/relinker/FaultyZipFileFactory.java b/relinker/src/test/java/com/getkeepsafe/relinker/FaultyZipFileFactory.java new file mode 100644 index 0000000..b7768f5 --- /dev/null +++ b/relinker/src/test/java/com/getkeepsafe/relinker/FaultyZipFileFactory.java @@ -0,0 +1,12 @@ +package com.getkeepsafe.relinker; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipFile; + +public class FaultyZipFileFactory implements ZipFileFactory { + @Override + public ZipFile create(File file, int openMode) throws IOException { + throw new IOException("Could not create zip file."); + } +} From 2e3f25cc110753c4ff58c4e4e3974acd5d79bedb Mon Sep 17 00:00:00 2001 From: Hadi Satrio Date: Thu, 2 May 2024 17:05:19 +0700 Subject: [PATCH 3/3] Include exception snippet in the report --- .../getkeepsafe/relinker/AbiSupportInfo.java | 17 +++++++++-------- .../relinker/ApkLibraryInstaller.java | 8 +++++--- .../relinker/MissingLibraryException.java | 2 +- .../relinker/ApkLibraryInstallerTest.java | 2 +- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java b/relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java index 6bb11d6..fae6560 100644 --- a/relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java +++ b/relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java @@ -2,19 +2,20 @@ import java.io.File; import java.util.Collections; +import java.util.Map; import java.util.Set; public class AbiSupportInfo { private final Set supportedAbis; - private final Set unScannableFiles; + private final Map unScannableFileAndReasons; public AbiSupportInfo(Set supportedAbis) { - this(supportedAbis, Collections.emptySet()); + this(supportedAbis, Collections.emptyMap()); } - public AbiSupportInfo(Set supportedAbis, Set unscannableFiles) { + public AbiSupportInfo(Set supportedAbis, Map unscannableFilesAndReasons) { this.supportedAbis = supportedAbis; - this.unScannableFiles = unscannableFiles; + this.unScannableFileAndReasons = unscannableFilesAndReasons; } public String[] getSupportedAbis() { @@ -23,11 +24,11 @@ public String[] getSupportedAbis() { return arr; } - public String[] getUnscannableFileNames() { - String[] arr = new String[unScannableFiles.size()]; + public String[] getUnscannableFileNameAndReasons() { + String[] arr = new String[unScannableFileAndReasons.size()]; int i = 0; - for (File file : unScannableFiles) { - arr[i++] = file.getName(); + for (Map.Entry entry : unScannableFileAndReasons.entrySet()) { + arr[i++] = entry.getKey().getName() + " => " + entry.getValue(); } return arr; } diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java b/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java index 5852c37..bb938cd 100644 --- a/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java +++ b/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java @@ -29,7 +29,9 @@ import java.io.OutputStream; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -132,13 +134,13 @@ private AbiSupportInfo getSupportedABIs(Context context, String mappedLibraryNam Pattern pattern = Pattern.compile(p); ZipFile zipFile; Set supportedABIs = new HashSet(); - Set unscannableFiles = new HashSet(); + Map unscannableFiles = new HashMap(); for (String sourceDir : sourceDirectories(context)) { File source = new File(sourceDir); try { zipFile = zipFileFactory.create(source, ZipFile.OPEN_READ); - } catch (IOException ignored) { - unscannableFiles.add(source); + } catch (IOException e) { + unscannableFiles.put(source, e); continue; } diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java b/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java index 8ae6342..36a95a0 100755 --- a/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java +++ b/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java @@ -35,7 +35,7 @@ public String getMessage() { sb.append("Looked for: ").append(Arrays.toString(wantedABIs)).append(", "); sb.append("but only found: ").append(Arrays.toString(abiSupportInfo.getSupportedAbis())).append("."); - String[] unscannableFileNames = abiSupportInfo.getUnscannableFileNames(); + String[] unscannableFileNames = abiSupportInfo.getUnscannableFileNameAndReasons(); if (unscannableFileNames.length != 0) { sb.append(" Additionally, encountered errors while scanning: ").append(Arrays.toString(unscannableFileNames)).append("."); } diff --git a/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java b/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java index be5d972..2c563fc 100644 --- a/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java +++ b/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java @@ -95,7 +95,7 @@ public void throwsMissingLibraryExceptionWhenABIIsMissingAndThereWereUnscannable try { installer.installLibrary(context, abis, "libtest.so", destination, instance); } catch (MissingLibraryException e) { - assertEquals("Could not find 'libtest.so'. Looked for: [armeabi-v7a], but only found: []. Additionally, encountered errors while scanning: [fake.apk].", e.getMessage()); + assertEquals("Could not find 'libtest.so'. Looked for: [armeabi-v7a], but only found: []. Additionally, encountered errors while scanning: [fake.apk => java.io.IOException: Could not create zip file.].", e.getMessage()); } }