diff options
author | Eric Holk <eholk@google.com> | 2018-12-13 16:04:56 -0800 |
---|---|---|
committer | Eric Holk <eholk@google.com> | 2018-12-14 09:36:14 -0800 |
commit | 3cbf176e7c6a96c535f56cb7d10e2de79a54cbd0 (patch) | |
tree | 405baf89c19d18830dfe7f8111d99ad219485667 /startop/view_compiler | |
parent | 657d61220648e924b24973c62f984d8654f7bd1f (diff) |
[view-compiler] Add layout validation
Layout validation walks over a layout resource and verifies that the layout only
uses features the layout compiler supports.
Currently this means we reject layouts that use any of the following tags:
* include
* fragment
* merge
* view
In the future, we will extend the layout compiler to handle these, but for now
we want to be liberal in what we reject.
Bug: 111895153
Test: atest view-compiler-tests
Change-Id: I8dca30c035a83b6763497a36fc60c68438fa1b0c
Diffstat (limited to 'startop/view_compiler')
-rw-r--r-- | startop/view_compiler/Android.bp | 7 | ||||
-rw-r--r-- | startop/view_compiler/layout_validation.cc | 42 | ||||
-rw-r--r-- | startop/view_compiler/layout_validation.h | 46 | ||||
-rw-r--r-- | startop/view_compiler/layout_validation_test.cc | 163 | ||||
-rw-r--r-- | startop/view_compiler/main.cc | 7 | ||||
-rw-r--r-- | startop/view_compiler/tinyxml_layout_parser.cc | 34 | ||||
-rw-r--r-- | startop/view_compiler/tinyxml_layout_parser.h | 65 |
7 files changed, 363 insertions, 1 deletions
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp index de40e0df48e7..91cec554d7cd 100644 --- a/startop/view_compiler/Android.bp +++ b/startop/view_compiler/Android.bp @@ -24,6 +24,9 @@ cc_defaults { "libdexfile", "slicer", ], + static_libs: [ + "libtinyxml2", + ], } cc_library_host_static { @@ -32,7 +35,9 @@ cc_library_host_static { srcs: [ "dex_builder.cc", "java_lang_builder.cc", + "tinyxml_layout_parser.cc", "util.cc", + "layout_validation.cc", ], } @@ -43,7 +48,6 @@ cc_binary_host { "main.cc", ], static_libs: [ - "libtinyxml2", "libgflags", "libviewcompiler", ], @@ -54,6 +58,7 @@ cc_test_host { defaults: ["viewcompiler_defaults"], srcs: [ "dex_builder_test.cc", + "layout_validation_test.cc", "util_test.cc", ], static_libs: [ diff --git a/startop/view_compiler/layout_validation.cc b/startop/view_compiler/layout_validation.cc new file mode 100644 index 000000000000..8c7737749124 --- /dev/null +++ b/startop/view_compiler/layout_validation.cc @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "layout_validation.h" + +#include "android-base/stringprintf.h" + +namespace startop { + +void LayoutValidationVisitor::VisitStartTag(const std::u16string& name) { + if (0 == name.compare(u"merge")) { + message_ = "Merge tags are not supported"; + can_compile_ = false; + } + if (0 == name.compare(u"include")) { + message_ = "Include tags are not supported"; + can_compile_ = false; + } + if (0 == name.compare(u"view")) { + message_ = "View tags are not supported"; + can_compile_ = false; + } + if (0 == name.compare(u"fragment")) { + message_ = "Fragment tags are not supported"; + can_compile_ = false; + } +} + +} // namespace startop
\ No newline at end of file diff --git a/startop/view_compiler/layout_validation.h b/startop/view_compiler/layout_validation.h new file mode 100644 index 000000000000..bed34bb38e5e --- /dev/null +++ b/startop/view_compiler/layout_validation.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LAYOUT_VALIDATION_H_ +#define LAYOUT_VALIDATION_H_ + +#include "dex_builder.h" + +#include <string> + +namespace startop { + +// This visitor determines whether a layout can be compiled. Since we do not currently support all +// features, such as includes and merges, we need to pre-validate the layout before we start +// compiling. +class LayoutValidationVisitor { + public: + void VisitStartDocument() const {} + void VisitEndDocument() const {} + void VisitStartTag(const std::u16string& name); + void VisitEndTag() const {} + + const std::string& message() const { return message_; } + bool can_compile() const { return can_compile_; } + + private: + std::string message_{"Okay"}; + bool can_compile_{true}; +}; + +} // namespace startop + +#endif // LAYOUT_VALIDATION_H_ diff --git a/startop/view_compiler/layout_validation_test.cc b/startop/view_compiler/layout_validation_test.cc new file mode 100644 index 000000000000..b74cdae8d725 --- /dev/null +++ b/startop/view_compiler/layout_validation_test.cc @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "tinyxml_layout_parser.h" + +#include "gtest/gtest.h" + +using startop::CanCompileLayout; +using std::string; + +namespace { +void ValidateXmlText(const string& xml, bool expected) { + tinyxml2::XMLDocument doc; + doc.Parse(xml.c_str()); + EXPECT_EQ(CanCompileLayout(doc), expected); +} +} // namespace + +TEST(LayoutValidationTest, SingleButtonLayout) { + const string xml = R"(<?xml version="1.0" encoding="utf-8"?> +<Button xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="Hello, World!"> + +</Button>)"; + ValidateXmlText(xml, /*expected=*/true); +} + +TEST(LayoutValidationTest, SmallConstraintLayout) { + const string xml = R"(<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button + android:id="@+id/button6" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:layout_marginBottom="16dp" + android:text="Button" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + + <Button + android:id="@+id/button7" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:layout_marginBottom="16dp" + android:text="Button2" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/button6" /> + + <Button + android:id="@+id/button8" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:layout_marginBottom="16dp" + android:text="Button1" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/button7" /> +</android.support.constraint.ConstraintLayout>)"; + ValidateXmlText(xml, /*expected=*/true); +} + +TEST(LayoutValidationTest, MergeNode) { + const string xml = R"(<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <TextView + android:id="@+id/textView3" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="TextView" /> + + <Button + android:id="@+id/button9" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> +</merge>)"; + ValidateXmlText(xml, /*expected=*/false); +} + +TEST(LayoutValidationTest, IncludeLayout) { + const string xml = R"(<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <include + layout="@layout/single_button_layout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> +</android.support.constraint.ConstraintLayout>)"; + ValidateXmlText(xml, /*expected=*/false); +} + +TEST(LayoutValidationTest, ViewNode) { + const string xml = R"(<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <view + class="android.support.design.button.MaterialButton" + id="@+id/view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> +</android.support.constraint.ConstraintLayout>)"; + ValidateXmlText(xml, /*expected=*/false); +} + +TEST(LayoutValidationTest, FragmentNode) { + // This test case is from https://developer.android.com/guide/components/fragments + const string xml = R"(<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <fragment android:name="com.example.news.ArticleListFragment" + android:id="@+id/list" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="match_parent" /> + <fragment android:name="com.example.news.ArticleReaderFragment" + android:id="@+id/viewer" + android:layout_weight="2" + android:layout_width="0dp" + android:layout_height="match_parent" /> +</LinearLayout>)"; + ValidateXmlText(xml, /*expected=*/false); +} diff --git a/startop/view_compiler/main.cc b/startop/view_compiler/main.cc index 7d791c229a98..9351dc34ea54 100644 --- a/startop/view_compiler/main.cc +++ b/startop/view_compiler/main.cc @@ -18,6 +18,7 @@ #include "dex_builder.h" #include "java_lang_builder.h" +#include "tinyxml_layout_parser.h" #include "util.h" #include "tinyxml2.h" @@ -100,6 +101,12 @@ int main(int argc, char** argv) { XMLDocument xml; xml.LoadFile(filename); + string message{}; + if (!startop::CanCompileLayout(xml, &message)) { + LOG(ERROR) << "Layout not supported: " << message; + return 1; + } + std::ofstream outfile; if (FLAGS_out != kStdoutFilename) { outfile.open(FLAGS_out); diff --git a/startop/view_compiler/tinyxml_layout_parser.cc b/startop/view_compiler/tinyxml_layout_parser.cc new file mode 100644 index 000000000000..1b3a81f17976 --- /dev/null +++ b/startop/view_compiler/tinyxml_layout_parser.cc @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "tinyxml_layout_parser.h" + +#include "layout_validation.h" + +namespace startop { + +bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message) { + LayoutValidationVisitor validator; + TinyXmlVisitorAdapter adapter{&validator}; + xml.Accept(&adapter); + + if (message != nullptr) { + *message = validator.message(); + } + + return validator.can_compile(); +} + +} // namespace startop diff --git a/startop/view_compiler/tinyxml_layout_parser.h b/startop/view_compiler/tinyxml_layout_parser.h new file mode 100644 index 000000000000..8f714a2c5a3f --- /dev/null +++ b/startop/view_compiler/tinyxml_layout_parser.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TINYXML_LAYOUT_PARSER_H_ +#define TINYXML_LAYOUT_PARSER_H_ + +#include "tinyxml2.h" + +#include <codecvt> +#include <locale> +#include <string> + +namespace startop { + +template <typename Visitor> +class TinyXmlVisitorAdapter : public tinyxml2::XMLVisitor { + public: + explicit TinyXmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {} + + bool VisitEnter(const tinyxml2::XMLDocument& /*doc*/) override { + visitor_->VisitStartDocument(); + return true; + } + + bool VisitExit(const tinyxml2::XMLDocument& /*doc*/) override { + visitor_->VisitEndDocument(); + return true; + } + + bool VisitEnter(const tinyxml2::XMLElement& element, + const tinyxml2::XMLAttribute* /*firstAttribute*/) override { + visitor_->VisitStartTag( + std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes( + element.Name())); + return true; + } + + bool VisitExit(const tinyxml2::XMLElement& /*element*/) override { + visitor_->VisitEndTag(); + return true; + } + + private: + Visitor* visitor_; +}; + +// Returns whether a layout resource represented by a TinyXML document is supported by the layout +// compiler. +bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message = nullptr); + +} // namespace startop + +#endif // TINYXML_LAYOUT_PARSER_H_ |