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#include "util/Util.h"
19#include "xml/XmlDom.h"
20
21#include <memory>
22#include <string>
23
24namespace aapt {
25namespace proguard {
26
27class BaseVisitor : public xml::Visitor {
28public:
29    BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) {
30    }
31
32    virtual void visit(xml::Text*) override {};
33
34    virtual void visit(xml::Namespace* node) override {
35        for (const auto& child : node->children) {
36            child->accept(this);
37        }
38    }
39
40    virtual void visit(xml::Element* node) override {
41        if (!node->namespaceUri.empty()) {
42            Maybe<xml::ExtractedPackage> maybePackage = xml::extractPackageFromNamespace(
43                    node->namespaceUri);
44            if (maybePackage) {
45                // This is a custom view, let's figure out the class name from this.
46                std::u16string package = maybePackage.value().package + u"." + node->name;
47                if (util::isJavaClassName(package)) {
48                    addClass(node->lineNumber, package);
49                }
50            }
51        } else if (util::isJavaClassName(node->name)) {
52            addClass(node->lineNumber, node->name);
53        }
54
55        for (const auto& child: node->children) {
56            child->accept(this);
57        }
58    }
59
60protected:
61    void addClass(size_t lineNumber, const std::u16string& className) {
62        mKeepSet->addClass(Source(mSource.path, lineNumber), className);
63    }
64
65    void addMethod(size_t lineNumber, const std::u16string& methodName) {
66        mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName);
67    }
68
69private:
70    Source mSource;
71    KeepSet* mKeepSet;
72};
73
74struct LayoutVisitor : public BaseVisitor {
75    LayoutVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
76    }
77
78    virtual void visit(xml::Element* node) override {
79        bool checkClass = false;
80        bool checkName = false;
81        if (node->namespaceUri.empty()) {
82            checkClass = node->name == u"view" || node->name == u"fragment";
83        } else if (node->namespaceUri == xml::kSchemaAndroid) {
84            checkName = node->name == u"fragment";
85        }
86
87        for (const auto& attr : node->attributes) {
88            if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" &&
89                    util::isJavaClassName(attr.value)) {
90                addClass(node->lineNumber, attr.value);
91            } else if (checkName && attr.namespaceUri == xml::kSchemaAndroid &&
92                    attr.name == u"name" && util::isJavaClassName(attr.value)) {
93                addClass(node->lineNumber, attr.value);
94            } else if (attr.namespaceUri == xml::kSchemaAndroid && attr.name == u"onClick") {
95                addMethod(node->lineNumber, attr.value);
96            }
97        }
98
99        BaseVisitor::visit(node);
100    }
101};
102
103struct XmlResourceVisitor : public BaseVisitor {
104    XmlResourceVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
105    }
106
107    virtual void visit(xml::Element* node) override {
108        bool checkFragment = false;
109        if (node->namespaceUri.empty()) {
110            checkFragment = node->name == u"PreferenceScreen" || node->name == u"header";
111        }
112
113        if (checkFragment) {
114            xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"fragment");
115            if (attr && util::isJavaClassName(attr->value)) {
116                addClass(node->lineNumber, attr->value);
117            }
118        }
119
120        BaseVisitor::visit(node);
121    }
122};
123
124struct TransitionVisitor : public BaseVisitor {
125    TransitionVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
126    }
127
128    virtual void visit(xml::Element* node) override {
129        bool checkClass = node->namespaceUri.empty() &&
130                (node->name == u"transition" || node->name == u"pathMotion");
131        if (checkClass) {
132            xml::Attribute* attr = node->findAttribute({}, u"class");
133            if (attr && util::isJavaClassName(attr->value)) {
134                addClass(node->lineNumber, attr->value);
135            }
136        }
137
138        BaseVisitor::visit(node);
139    }
140};
141
142struct ManifestVisitor : public BaseVisitor {
143    ManifestVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
144    }
145
146    virtual void visit(xml::Element* node) override {
147        if (node->namespaceUri.empty()) {
148            bool getName = false;
149            if (node->name == u"manifest") {
150                xml::Attribute* attr = node->findAttribute({}, u"package");
151                if (attr) {
152                    mPackage = attr->value;
153                }
154            } else if (node->name == u"application") {
155                getName = true;
156                xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"backupAgent");
157                if (attr) {
158                    Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
159                                                                                    attr->value);
160                    if (result) {
161                        addClass(node->lineNumber, result.value());
162                    }
163                }
164            } else if (node->name == u"activity" || node->name == u"service" ||
165                    node->name == u"receiver" || node->name == u"provider" ||
166                    node->name == u"instrumentation") {
167                getName = true;
168            }
169
170            if (getName) {
171                xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"name");
172                if (attr) {
173                    Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
174                                                                                    attr->value);
175                    if (result) {
176                        addClass(node->lineNumber, result.value());
177                    }
178                }
179            }
180        }
181        BaseVisitor::visit(node);
182    }
183
184    std::u16string mPackage;
185};
186
187bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res,
188                                     KeepSet* keepSet) {
189    ManifestVisitor visitor(source, keepSet);
190    if (res->root) {
191        res->root->accept(&visitor);
192        return true;
193    }
194    return false;
195}
196
197bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet) {
198    if (!res->root) {
199        return false;
200    }
201
202    switch (res->file.name.type) {
203        case ResourceType::kLayout: {
204            LayoutVisitor visitor(source, keepSet);
205            res->root->accept(&visitor);
206            break;
207        }
208
209        case ResourceType::kXml: {
210            XmlResourceVisitor visitor(source, keepSet);
211            res->root->accept(&visitor);
212            break;
213        }
214
215        case ResourceType::kTransition: {
216            TransitionVisitor visitor(source, keepSet);
217            res->root->accept(&visitor);
218            break;
219        }
220
221        default:
222            break;
223    }
224    return true;
225}
226
227bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
228    for (const auto& entry : keepSet.mKeepSet) {
229        for (const Source& source : entry.second) {
230            *out << "# Referenced at " << source << "\n";
231        }
232        *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
233    }
234
235    for (const auto& entry : keepSet.mKeepMethodSet) {
236        for (const Source& source : entry.second) {
237            *out << "# Referenced at " << source << "\n";
238        }
239        *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
240    }
241    return true;
242}
243
244} // namespace proguard
245} // namespace aapt
246