1 /* 2 * Copyright (C) 2015 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 17 #include "java/ProguardRules.h" 18 19 #include <memory> 20 #include <string> 21 22 #include "android-base/macros.h" 23 #include "androidfw/StringPiece.h" 24 25 #include "JavaClassGenerator.h" 26 #include "ResourceUtils.h" 27 #include "ValueVisitor.h" 28 #include "text/Printer.h" 29 #include "util/Util.h" 30 #include "xml/XmlDom.h" 31 32 using ::aapt::io::OutputStream; 33 using ::aapt::text::Printer; 34 35 namespace aapt { 36 namespace proguard { 37 38 class BaseVisitor : public xml::Visitor { 39 public: 40 using xml::Visitor::Visit; 41 42 BaseVisitor(const ResourceFile& file, KeepSet* keep_set) : file_(file), keep_set_(keep_set) { 43 } 44 45 void Visit(xml::Element* node) override { 46 if (!node->namespace_uri.empty()) { 47 Maybe<xml::ExtractedPackage> maybe_package = 48 xml::ExtractPackageFromNamespace(node->namespace_uri); 49 if (maybe_package) { 50 // This is a custom view, let's figure out the class name from this. 51 std::string package = maybe_package.value().package + "." + node->name; 52 if (util::IsJavaClassName(package)) { 53 AddClass(node->line_number, package); 54 } 55 } 56 } else if (util::IsJavaClassName(node->name)) { 57 AddClass(node->line_number, node->name); 58 } 59 60 for (const auto& child : node->children) { 61 child->Accept(this); 62 } 63 64 for (const auto& attr : node->attributes) { 65 if (attr.compiled_value) { 66 auto ref = ValueCast<Reference>(attr.compiled_value.get()); 67 if (ref) { 68 AddReference(node->line_number, ref); 69 } 70 } 71 } 72 } 73 74 protected: 75 ResourceFile file_; 76 KeepSet* keep_set_; 77 78 virtual void AddClass(size_t line_number, const std::string& class_name) { 79 keep_set_->AddConditionalClass({file_.name, file_.source.WithLine(line_number)}, class_name); 80 } 81 82 void AddMethod(size_t line_number, const std::string& method_name) { 83 keep_set_->AddMethod({file_.name, file_.source.WithLine(line_number)}, method_name); 84 } 85 86 void AddReference(size_t line_number, Reference* ref) { 87 if (ref && ref->name) { 88 ResourceName ref_name = ref->name.value(); 89 if (ref_name.package.empty()) { 90 ref_name = ResourceName(file_.name.package, ref_name.type, ref_name.entry); 91 } 92 keep_set_->AddReference({file_.name, file_.source.WithLine(line_number)}, ref_name); 93 } 94 } 95 96 private: 97 DISALLOW_COPY_AND_ASSIGN(BaseVisitor); 98 99 }; 100 101 class LayoutVisitor : public BaseVisitor { 102 public: 103 LayoutVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { 104 } 105 106 void Visit(xml::Element* node) override { 107 bool check_class = false; 108 bool check_name = false; 109 if (node->namespace_uri.empty()) { 110 if (node->name == "view") { 111 check_class = true; 112 } else if (node->name == "fragment") { 113 check_class = check_name = true; 114 } 115 } else if (node->namespace_uri == xml::kSchemaAndroid) { 116 check_name = node->name == "fragment"; 117 } 118 119 for (const auto& attr : node->attributes) { 120 if (check_class && attr.namespace_uri.empty() && attr.name == "class" && 121 util::IsJavaClassName(attr.value)) { 122 AddClass(node->line_number, attr.value); 123 } else if (check_name && attr.namespace_uri == xml::kSchemaAndroid && 124 attr.name == "name" && util::IsJavaClassName(attr.value)) { 125 AddClass(node->line_number, attr.value); 126 } else if (attr.namespace_uri == xml::kSchemaAndroid && 127 attr.name == "onClick") { 128 AddMethod(node->line_number, attr.value); 129 } 130 } 131 132 BaseVisitor::Visit(node); 133 } 134 135 private: 136 DISALLOW_COPY_AND_ASSIGN(LayoutVisitor); 137 }; 138 139 class MenuVisitor : public BaseVisitor { 140 public: 141 MenuVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { 142 } 143 144 void Visit(xml::Element* node) override { 145 if (node->namespace_uri.empty() && node->name == "item") { 146 for (const auto& attr : node->attributes) { 147 if (attr.namespace_uri == xml::kSchemaAndroid) { 148 if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") && 149 util::IsJavaClassName(attr.value)) { 150 AddClass(node->line_number, attr.value); 151 } else if (attr.name == "onClick") { 152 AddMethod(node->line_number, attr.value); 153 } 154 } 155 } 156 } 157 158 BaseVisitor::Visit(node); 159 } 160 161 private: 162 DISALLOW_COPY_AND_ASSIGN(MenuVisitor); 163 }; 164 165 class XmlResourceVisitor : public BaseVisitor { 166 public: 167 XmlResourceVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { 168 } 169 170 void Visit(xml::Element* node) override { 171 bool check_fragment = false; 172 if (node->namespace_uri.empty()) { 173 check_fragment = 174 node->name == "PreferenceScreen" || node->name == "header"; 175 } 176 177 if (check_fragment) { 178 xml::Attribute* attr = 179 node->FindAttribute(xml::kSchemaAndroid, "fragment"); 180 if (attr && util::IsJavaClassName(attr->value)) { 181 AddClass(node->line_number, attr->value); 182 } 183 } 184 185 BaseVisitor::Visit(node); 186 } 187 188 private: 189 DISALLOW_COPY_AND_ASSIGN(XmlResourceVisitor); 190 }; 191 192 class TransitionVisitor : public BaseVisitor { 193 public: 194 TransitionVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { 195 } 196 197 void Visit(xml::Element* node) override { 198 bool check_class = 199 node->namespace_uri.empty() && (node->name == "transition" || node->name == "pathMotion"); 200 if (check_class) { 201 xml::Attribute* attr = node->FindAttribute({}, "class"); 202 if (attr && util::IsJavaClassName(attr->value)) { 203 AddClass(node->line_number, attr->value); 204 } 205 } 206 207 BaseVisitor::Visit(node); 208 } 209 210 private: 211 DISALLOW_COPY_AND_ASSIGN(TransitionVisitor); 212 }; 213 214 class ManifestVisitor : public BaseVisitor { 215 public: 216 ManifestVisitor(const ResourceFile& file, KeepSet* keep_set, bool main_dex_only) 217 : BaseVisitor(file, keep_set), main_dex_only_(main_dex_only) { 218 } 219 220 void Visit(xml::Element* node) override { 221 if (node->namespace_uri.empty()) { 222 bool get_name = false; 223 if (node->name == "manifest") { 224 xml::Attribute* attr = node->FindAttribute({}, "package"); 225 if (attr) { 226 package_ = attr->value; 227 } 228 } else if (node->name == "application") { 229 get_name = true; 230 xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "backupAgent"); 231 if (attr) { 232 Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value); 233 if (result) { 234 AddClass(node->line_number, result.value()); 235 } 236 } 237 if (main_dex_only_) { 238 xml::Attribute* default_process = node->FindAttribute(xml::kSchemaAndroid, "process"); 239 if (default_process) { 240 default_process_ = default_process->value; 241 } 242 } 243 } else if (node->name == "activity" || node->name == "service" || 244 node->name == "receiver" || node->name == "provider") { 245 get_name = true; 246 247 if (main_dex_only_) { 248 xml::Attribute* component_process = node->FindAttribute(xml::kSchemaAndroid, "process"); 249 250 const std::string& process = 251 component_process ? component_process->value : default_process_; 252 get_name = !process.empty() && process[0] != ':'; 253 } 254 } else if (node->name == "instrumentation") { 255 get_name = true; 256 } 257 258 if (get_name) { 259 xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "name"); 260 get_name = attr != nullptr; 261 262 if (get_name) { 263 Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value); 264 if (result) { 265 AddClass(node->line_number, result.value()); 266 } 267 } 268 } 269 } 270 BaseVisitor::Visit(node); 271 } 272 273 virtual void AddClass(size_t line_number, const std::string& class_name) override { 274 keep_set_->AddManifestClass({file_.name, file_.source.WithLine(line_number)}, class_name); 275 } 276 277 private: 278 DISALLOW_COPY_AND_ASSIGN(ManifestVisitor); 279 280 std::string package_; 281 const bool main_dex_only_; 282 std::string default_process_; 283 }; 284 285 bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only) { 286 ManifestVisitor visitor(res->file, keep_set, main_dex_only); 287 if (res->root) { 288 res->root->Accept(&visitor); 289 return true; 290 } 291 return false; 292 } 293 294 bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set) { 295 if (!res->root) { 296 return false; 297 } 298 299 switch (res->file.name.type) { 300 case ResourceType::kLayout: { 301 LayoutVisitor visitor(res->file, keep_set); 302 res->root->Accept(&visitor); 303 break; 304 } 305 306 case ResourceType::kXml: { 307 XmlResourceVisitor visitor(res->file, keep_set); 308 res->root->Accept(&visitor); 309 break; 310 } 311 312 case ResourceType::kTransition: { 313 TransitionVisitor visitor(res->file, keep_set); 314 res->root->Accept(&visitor); 315 break; 316 } 317 318 case ResourceType::kMenu: { 319 MenuVisitor visitor(res->file, keep_set); 320 res->root->Accept(&visitor); 321 break; 322 } 323 324 default: { 325 BaseVisitor visitor(res->file, keep_set); 326 res->root->Accept(&visitor); 327 break; 328 } 329 } 330 return true; 331 } 332 333 void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) { 334 Printer printer(out); 335 for (const auto& entry : keep_set.manifest_class_set_) { 336 for (const UsageLocation& location : entry.second) { 337 printer.Print("# Referenced at ").Println(location.source.to_string()); 338 } 339 printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); 340 } 341 342 for (const auto& entry : keep_set.conditional_class_set_) { 343 std::set<UsageLocation> locations; 344 bool can_be_conditional = true; 345 for (const UsageLocation& location : entry.second) { 346 can_be_conditional &= CollectLocations(location, keep_set, &locations); 347 } 348 349 if (keep_set.conditional_keep_rules_ && can_be_conditional) { 350 for (const UsageLocation& location : locations) { 351 printer.Print("# Referenced at ").Println(location.source.to_string()); 352 printer.Print("-if class **.R$layout { int ") 353 .Print(JavaClassGenerator::TransformToFieldName(location.name.entry)) 354 .Println("; }"); 355 printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); 356 } 357 } else { 358 for (const UsageLocation& location : entry.second) { 359 printer.Print("# Referenced at ").Println(location.source.to_string()); 360 } 361 printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); 362 } 363 printer.Println(); 364 } 365 366 for (const auto& entry : keep_set.method_set_) { 367 for (const UsageLocation& location : entry.second) { 368 printer.Print("# Referenced at ").Println(location.source.to_string()); 369 } 370 printer.Print("-keepclassmembers class * { *** ").Print(entry.first).Println("(...); }"); 371 printer.Println(); 372 } 373 } 374 375 bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, 376 std::set<UsageLocation>* locations) { 377 locations->insert(location); 378 379 // TODO: allow for more reference types if we can determine its safe. 380 if (location.name.type != ResourceType::kLayout) { 381 return false; 382 } 383 384 for (const auto& entry : keep_set.reference_set_) { 385 if (entry.first == location.name) { 386 for (auto& refLocation : entry.second) { 387 // Don't get stuck in loops 388 if (locations->find(refLocation) != locations->end()) { 389 return false; 390 } 391 if (!CollectLocations(refLocation, keep_set, locations)) { 392 return false; 393 } 394 } 395 } 396 } 397 398 return true; 399 } 400 401 class ReferenceVisitor : public ValueVisitor { 402 public: 403 using ValueVisitor::Visit; 404 405 ReferenceVisitor(aapt::IAaptContext* context, ResourceName from, KeepSet* keep_set) 406 : context_(context), from_(from), keep_set_(keep_set) { 407 } 408 409 void Visit(Reference* reference) override { 410 if (reference->name) { 411 ResourceName reference_name = reference->name.value(); 412 if (reference_name.package.empty()) { 413 reference_name = ResourceName(context_->GetCompilationPackage(), reference_name.type, 414 reference_name.entry); 415 } 416 keep_set_->AddReference({from_, reference->GetSource()}, reference_name); 417 } 418 } 419 420 private: 421 aapt::IAaptContext* context_; 422 ResourceName from_; 423 KeepSet* keep_set_; 424 }; 425 426 bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table, 427 KeepSet* keep_set) { 428 for (auto& pkg : table->packages) { 429 for (auto& type : pkg->types) { 430 for (auto& entry : type->entries) { 431 for (auto& config_value : entry->values) { 432 ResourceName from(pkg->name, type->type, entry->name); 433 ReferenceVisitor visitor(context, from, keep_set); 434 config_value->value->Accept(&visitor); 435 } 436 } 437 } 438 } 439 return true; 440 } 441 442 } // namespace proguard 443 } // namespace aapt 444