1 /*
2 * Copyright 2023 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 "link/FeatureFlagsFilter.h"
18
19 #include <string_view>
20
21 #include "androidfw/IDiagnostics.h"
22 #include "androidfw/Source.h"
23 #include "util/Util.h"
24 #include "xml/XmlDom.h"
25 #include "xml/XmlUtil.h"
26
27 using ::aapt::xml::Element;
28 using ::aapt::xml::Node;
29 using ::aapt::xml::NodeCast;
30
31 namespace aapt {
32
33 class FlagsVisitor : public xml::Visitor {
34 public:
FlagsVisitor(android::IDiagnostics * diagnostics,const FeatureFlagValues & feature_flag_values,const FeatureFlagsFilterOptions & options)35 explicit FlagsVisitor(android::IDiagnostics* diagnostics,
36 const FeatureFlagValues& feature_flag_values,
37 const FeatureFlagsFilterOptions& options)
38 : diagnostics_(diagnostics), feature_flag_values_(feature_flag_values), options_(options) {
39 }
40
Visit(xml::Element * node)41 void Visit(xml::Element* node) override {
42 std::erase_if(node->children,
43 [this](std::unique_ptr<xml::Node>& node) { return ShouldRemove(node); });
44 VisitChildren(node);
45 }
46
HasError() const47 bool HasError() const {
48 return has_error_;
49 }
50
51 private:
ShouldRemove(std::unique_ptr<xml::Node> & node)52 bool ShouldRemove(std::unique_ptr<xml::Node>& node) {
53 if (const auto* el = NodeCast<Element>(node.get())) {
54 auto* attr = el->FindAttribute(xml::kSchemaAndroid, "featureFlag");
55 if (attr == nullptr) {
56 return false;
57 }
58
59 bool negated = false;
60 std::string_view flag_name = util::TrimWhitespace(attr->value);
61 if (flag_name.starts_with('!')) {
62 negated = true;
63 flag_name = flag_name.substr(1);
64 }
65
66 if (auto it = feature_flag_values_.find(flag_name); it != feature_flag_values_.end()) {
67 if (it->second.enabled.has_value()) {
68 if (options_.flags_must_be_readonly && !it->second.read_only) {
69 diagnostics_->Error(android::DiagMessage(node->line_number)
70 << "attribute 'android:featureFlag' has flag '" << flag_name
71 << "' which must be readonly but is not");
72 has_error_ = true;
73 return false;
74 }
75 if (options_.remove_disabled_elements) {
76 // Remove if flag==true && attr=="!flag" (negated) OR flag==false && attr=="flag"
77 return *it->second.enabled == negated;
78 }
79 } else if (options_.flags_must_have_value) {
80 diagnostics_->Error(android::DiagMessage(node->line_number)
81 << "attribute 'android:featureFlag' has flag '" << flag_name
82 << "' without a true/false value from --feature_flags parameter");
83 has_error_ = true;
84 return false;
85 }
86 } else if (options_.fail_on_unrecognized_flags) {
87 diagnostics_->Error(android::DiagMessage(node->line_number)
88 << "attribute 'android:featureFlag' has flag '" << flag_name
89 << "' not found in flags from --feature_flags parameter");
90 has_error_ = true;
91 return false;
92 }
93 }
94
95 return false;
96 }
97
98 android::IDiagnostics* diagnostics_;
99 const FeatureFlagValues& feature_flag_values_;
100 const FeatureFlagsFilterOptions& options_;
101 bool has_error_ = false;
102 };
103
Consume(IAaptContext * context,xml::XmlResource * doc)104 bool FeatureFlagsFilter::Consume(IAaptContext* context, xml::XmlResource* doc) {
105 FlagsVisitor visitor(context->GetDiagnostics(), feature_flag_values_, options_);
106 doc->root->Accept(&visitor);
107 return !visitor.HasError();
108 }
109
110 } // namespace aapt
111