blob: ce1b39fd6a1dd798c98a7c6f8ff8911e7612d7c6 [file] [log] [blame]
Rico Winda6e4efc2023-08-03 07:51:44 +02001/*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.build.shrinker;
18
19import com.android.aapt.Resources.ResourceTable;
20import com.android.annotations.NonNull;
21import com.android.annotations.Nullable;
22import com.android.build.shrinker.obfuscation.ObfuscatedClasses;
23import com.android.ide.common.resources.usage.ResourceStore;
24import com.android.ide.common.resources.usage.ResourceUsageModel.Resource;
25import com.android.resources.ResourceType;
26import com.google.common.collect.Maps;
27import com.google.common.collect.Sets;
28import java.io.IOException;
29import java.io.UncheckedIOException;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.util.Map;
33import java.util.Set;
34import java.util.stream.Collectors;
35
36/**
37 * Represents resource shrinker state that is shared between shrinker stages ResourcesGatherer,
38 * ObfuscationMappingsRecorder, ResourceUsageRecorder, ResourcesGraphBuilder and each stage
39 * contributes an information via changing its state.
40 */
41public class ResourceShrinkerModel {
42
43 private final ShrinkerDebugReporter debugReporter;
44
45 private final ResourceStore resourceStore;
46 private ObfuscatedClasses obfuscatedClasses = ObfuscatedClasses.NO_OBFUSCATION;
47
48 private final Set<String> strings = Sets.newHashSetWithExpectedSize(300);
49
50 private final Map<String, ResourceTable> resourceTableCache = Maps.newHashMap();
51
52 private boolean foundGetIdentifier = false;
53 private boolean foundWebContent = false;
54
55 public ResourceShrinkerModel(
56 ShrinkerDebugReporter debugReporter, boolean supportMultipackages) {
57 this.debugReporter = debugReporter;
58 resourceStore = new ResourceStore(supportMultipackages);
59 }
60
61 @NonNull
62 public ShrinkerDebugReporter getDebugReporter() {
63 return debugReporter;
64 }
65
66 @NonNull
67 public ResourceStore getResourceStore() {
68 return resourceStore;
69 }
70
71 @NonNull
72 public ObfuscatedClasses getObfuscatedClasses() {
73 return obfuscatedClasses;
74 }
75
76 /** Reports recorded mappings from obfuscated classes to original classes */
77 public void setObfuscatedClasses(@NonNull ObfuscatedClasses obfuscatedClasses) {
78 this.obfuscatedClasses = obfuscatedClasses;
79 }
80
81 /** Adds a new gathered resource to model. */
82 @NonNull
83 public Resource addResource(
84 @NonNull ResourceType type,
85 @Nullable String packageName,
86 @NonNull String name,
87 @Nullable String value) {
88 int intValue = -1;
89 try {
90 intValue = value != null ? Integer.decode(value) : -1;
91 } catch (NumberFormatException e) {
92 // ignore
93 }
94 return addResource(type, packageName, name, intValue);
95 }
96
97 /** Adds a new gathered resource to model. */
98 @NonNull
99 public Resource addResource(
100 @NonNull ResourceType type,
101 @Nullable String packageName,
102 @NonNull String name,
103 int value) {
104 return resourceStore.addResource(new Resource(packageName, type, name, value));
105 }
106
107 /** Adds string constant found in code to strings pool. */
108 public void addStringConstant(@NonNull String string) {
109 strings.add(string);
110 }
111
112 /** Returns is reference to {@code Resources#getIdentifier} is found in code. */
113 public boolean isFoundGetIdentifier() {
114 return foundGetIdentifier;
115 }
116
117 /** Reports that reference to {@code Resources#getIdentifier} is found in code. */
118 public void setFoundGetIdentifier(boolean foundGetIdentifier) {
119 this.foundGetIdentifier = foundGetIdentifier;
120 }
121
122 /** Returns is web content is found in code. */
123 public boolean isFoundWebContent() {
124 return foundWebContent;
125 }
126
127 /** Reports that reference to web content is found in code. */
128 public void setFoundWebContent(boolean foundWebContent) {
129 this.foundWebContent = foundWebContent;
130 }
131
132 /** Returns all string constants gathered from compiled classes. */
133 public Set<String> getStrings() {
134 return strings;
135 }
136
137 /**
138 * Mark resources that match string constants as reachable in case invocation of
139 * {@code Resources#getIdentifier} or web content is found in code and safe mode in enabled.
140 */
141 public void keepPossiblyReferencedResources() {
142 if (strings.isEmpty()
143 || !resourceStore.getSafeMode()
144 || (!foundGetIdentifier && !foundWebContent)) {
145 // No calls to android.content.res.Resources#getIdentifier; or user specifically asked
146 // for us not to guess resources to keep
147 return;
148 }
149
150 debugReporter.debug(() -> ""
151 + "android.content.res.Resources#getIdentifier present: " + foundGetIdentifier + "\n"
152 + "Web content present: " + foundWebContent + "\n"
153 + "Referenced Strings:\n"
154 + strings.stream()
155 .map(s -> s.trim().replace("\n", "\\n"))
156 .filter(s -> !s.isEmpty())
157 .map(s -> s.length() > 40 ? s.substring(0, 37) + "..." : s)
158 .collect(Collectors.joining("\n"))
159 );
160
161 new PossibleResourcesMarker(debugReporter, resourceStore, strings, foundWebContent)
162 .markPossibleResourcesReachable();
163 }
164
165 /**
166 * Reads resource table from specified path and stores it in cache to be able to reuse it if
167 * the same resource table is requested by another unit.
168 */
169 public ResourceTable readResourceTable(Path resourceTablePath) {
170 return resourceTableCache.computeIfAbsent(resourceTablePath.toString(), (path) -> {
171 try {
172 return ResourceTable.parseFrom(Files.readAllBytes(resourceTablePath));
173 } catch (IOException e) {
174 throw new UncheckedIOException(e);
175 }
176 });
177 }
178}