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
24#include "util/Util.h"
25#include "xml/XmlDom.h"
26
27namespace aapt {
28namespace proguard {
29
30class BaseVisitor : public xml::Visitor {
31 public:
32  BaseVisitor(const Source& source, KeepSet* keep_set)
33      : source_(source), keep_set_(keep_set) {}
34
35  virtual void Visit(xml::Text*) override{};
36
37  virtual void Visit(xml::Namespace* node) override {
38    for (const auto& child : node->children) {
39      child->Accept(this);
40    }
41  }
42
43  virtual void Visit(xml::Element* node) override {
44    if (!node->namespace_uri.empty()) {
45      Maybe<xml::ExtractedPackage> maybe_package =
46          xml::ExtractPackageFromNamespace(node->namespace_uri);
47      if (maybe_package) {
48        // This is a custom view, let's figure out the class name from this.
49        std::string package = maybe_package.value().package + "." + node->name;
50        if (util::IsJavaClassName(package)) {
51          AddClass(node->line_number, package);
52        }
53      }
54    } else if (util::IsJavaClassName(node->name)) {
55      AddClass(node->line_number, node->name);
56    }
57
58    for (const auto& child : node->children) {
59      child->Accept(this);
60    }
61  }
62
63 protected:
64  void AddClass(size_t line_number, const std::string& class_name) {
65    keep_set_->AddClass(Source(source_.path, line_number), class_name);
66  }
67
68  void AddMethod(size_t line_number, const std::string& method_name) {
69    keep_set_->AddMethod(Source(source_.path, line_number), method_name);
70  }
71
72 private:
73  DISALLOW_COPY_AND_ASSIGN(BaseVisitor);
74
75  Source source_;
76  KeepSet* keep_set_;
77};
78
79class LayoutVisitor : public BaseVisitor {
80 public:
81  LayoutVisitor(const Source& source, KeepSet* keep_set)
82      : BaseVisitor(source, keep_set) {}
83
84  virtual void Visit(xml::Element* node) override {
85    bool check_class = false;
86    bool check_name = false;
87    if (node->namespace_uri.empty()) {
88      check_class = node->name == "view" || node->name == "fragment";
89    } else if (node->namespace_uri == xml::kSchemaAndroid) {
90      check_name = node->name == "fragment";
91    }
92
93    for (const auto& attr : node->attributes) {
94      if (check_class && attr.namespace_uri.empty() && attr.name == "class" &&
95          util::IsJavaClassName(attr.value)) {
96        AddClass(node->line_number, attr.value);
97      } else if (check_name && attr.namespace_uri == xml::kSchemaAndroid &&
98                 attr.name == "name" && util::IsJavaClassName(attr.value)) {
99        AddClass(node->line_number, attr.value);
100      } else if (attr.namespace_uri == xml::kSchemaAndroid &&
101                 attr.name == "onClick") {
102        AddMethod(node->line_number, attr.value);
103      }
104    }
105
106    BaseVisitor::Visit(node);
107  }
108
109 private:
110  DISALLOW_COPY_AND_ASSIGN(LayoutVisitor);
111};
112
113class XmlResourceVisitor : public BaseVisitor {
114 public:
115  XmlResourceVisitor(const Source& source, KeepSet* keep_set)
116      : BaseVisitor(source, keep_set) {}
117
118  virtual void Visit(xml::Element* node) override {
119    bool check_fragment = false;
120    if (node->namespace_uri.empty()) {
121      check_fragment =
122          node->name == "PreferenceScreen" || node->name == "header";
123    }
124
125    if (check_fragment) {
126      xml::Attribute* attr =
127          node->FindAttribute(xml::kSchemaAndroid, "fragment");
128      if (attr && util::IsJavaClassName(attr->value)) {
129        AddClass(node->line_number, attr->value);
130      }
131    }
132
133    BaseVisitor::Visit(node);
134  }
135
136 private:
137  DISALLOW_COPY_AND_ASSIGN(XmlResourceVisitor);
138};
139
140class TransitionVisitor : public BaseVisitor {
141 public:
142  TransitionVisitor(const Source& source, KeepSet* keep_set)
143      : BaseVisitor(source, keep_set) {}
144
145  virtual void Visit(xml::Element* node) override {
146    bool check_class =
147        node->namespace_uri.empty() &&
148        (node->name == "transition" || node->name == "pathMotion");
149    if (check_class) {
150      xml::Attribute* attr = node->FindAttribute({}, "class");
151      if (attr && util::IsJavaClassName(attr->value)) {
152        AddClass(node->line_number, attr->value);
153      }
154    }
155
156    BaseVisitor::Visit(node);
157  }
158
159 private:
160  DISALLOW_COPY_AND_ASSIGN(TransitionVisitor);
161};
162
163class ManifestVisitor : public BaseVisitor {
164 public:
165  ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only)
166      : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {}
167
168  virtual void Visit(xml::Element* node) override {
169    if (node->namespace_uri.empty()) {
170      bool get_name = false;
171      if (node->name == "manifest") {
172        xml::Attribute* attr = node->FindAttribute({}, "package");
173        if (attr) {
174          package_ = attr->value;
175        }
176      } else if (node->name == "application") {
177        get_name = true;
178        xml::Attribute* attr =
179            node->FindAttribute(xml::kSchemaAndroid, "backupAgent");
180        if (attr) {
181          Maybe<std::string> result =
182              util::GetFullyQualifiedClassName(package_, attr->value);
183          if (result) {
184            AddClass(node->line_number, result.value());
185          }
186        }
187        if (main_dex_only_) {
188          xml::Attribute* default_process =
189              node->FindAttribute(xml::kSchemaAndroid, "process");
190          if (default_process) {
191            default_process_ = default_process->value;
192          }
193        }
194      } else if (node->name == "activity" || node->name == "service" ||
195                 node->name == "receiver" || node->name == "provider") {
196        get_name = true;
197
198        if (main_dex_only_) {
199          xml::Attribute* component_process =
200              node->FindAttribute(xml::kSchemaAndroid, "process");
201
202          const std::string& process =
203              component_process ? component_process->value : default_process_;
204          get_name = !process.empty() && process[0] != ':';
205        }
206      } else if (node->name == "instrumentation") {
207        get_name = true;
208      }
209
210      if (get_name) {
211        xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "name");
212        get_name = attr != nullptr;
213
214        if (get_name) {
215          Maybe<std::string> result =
216              util::GetFullyQualifiedClassName(package_, attr->value);
217          if (result) {
218            AddClass(node->line_number, result.value());
219          }
220        }
221      }
222    }
223    BaseVisitor::Visit(node);
224  }
225
226 private:
227  DISALLOW_COPY_AND_ASSIGN(ManifestVisitor);
228
229  std::string package_;
230  const bool main_dex_only_;
231  std::string default_process_;
232};
233
234bool CollectProguardRulesForManifest(const Source& source,
235                                     xml::XmlResource* res, KeepSet* keep_set,
236                                     bool main_dex_only) {
237  ManifestVisitor visitor(source, keep_set, main_dex_only);
238  if (res->root) {
239    res->root->Accept(&visitor);
240    return true;
241  }
242  return false;
243}
244
245bool CollectProguardRules(const Source& source, xml::XmlResource* res,
246                          KeepSet* keep_set) {
247  if (!res->root) {
248    return false;
249  }
250
251  switch (res->file.name.type) {
252    case ResourceType::kLayout: {
253      LayoutVisitor visitor(source, keep_set);
254      res->root->Accept(&visitor);
255      break;
256    }
257
258    case ResourceType::kXml: {
259      XmlResourceVisitor visitor(source, keep_set);
260      res->root->Accept(&visitor);
261      break;
262    }
263
264    case ResourceType::kTransition: {
265      TransitionVisitor visitor(source, keep_set);
266      res->root->Accept(&visitor);
267      break;
268    }
269
270    default:
271      break;
272  }
273  return true;
274}
275
276bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) {
277  for (const auto& entry : keep_set.keep_set_) {
278    for (const Source& source : entry.second) {
279      *out << "# Referenced at " << source << "\n";
280    }
281    *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
282  }
283
284  for (const auto& entry : keep_set.keep_method_set_) {
285    for (const Source& source : entry.second) {
286      *out << "# Referenced at " << source << "\n";
287    }
288    *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n"
289         << std::endl;
290  }
291  return true;
292}
293
294}  // namespace proguard
295}  // namespace aapt
296