AAPT2: Allow undefined resources (placeholders)

A resource defined like so:

<item type="drawable" name="foo" />

should be assigned the value @null.

The only exception is for <string> resources, which are given the
empty string value (since <string></string> is ambiguous). The decision
to use "" is based off the fact that old AAPT used to assign "" to all
undefined resources, even non-string ones.

Bug: 38425050
Test: make aapt2_tests
Change-Id: Ib3e0f6f83d16ddd8b279c9fd44a07a37867b85e9
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 064d864..1dc47f8 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -160,7 +160,10 @@
 // ==========================================================
 cc_test_host {
     name: "aapt2_tests",
-    srcs: ["**/*_test.cpp"],
+    srcs: [
+        "test/Common.cpp",
+        "**/*_test.cpp",
+    ],
     static_libs: [
         "libaapt2",
         "libgmock",
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 084325a..9a37913 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -623,6 +623,11 @@
     return std::move(string);
   }
 
+  // If the text is empty, and the value is not allowed to be a string, encode it as a @null.
+  if (util::TrimWhitespace(raw_value).empty()) {
+    return ResourceUtils::MakeNull();
+  }
+
   if (allow_raw_value) {
     // We can't parse this so return a RawString if we are allowed.
     return util::make_unique<RawString>(
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 8258019..5631dc2 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -78,42 +78,32 @@
    * Item, then a
    * RawString is returned. Otherwise this returns false;
    */
-  std::unique_ptr<Item> ParseXml(xml::XmlPullParser* parser,
-                                 const uint32_t type_mask,
+  std::unique_ptr<Item> ParseXml(xml::XmlPullParser* parser, const uint32_t type_mask,
                                  const bool allow_raw_value);
 
   bool ParseResources(xml::XmlPullParser* parser);
   bool ParseResource(xml::XmlPullParser* parser, ParsedResource* out_resource);
 
-  bool ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource,
-                 uint32_t format);
+  bool ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource, uint32_t format);
   bool ParseString(xml::XmlPullParser* parser, ParsedResource* out_resource);
 
   bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
-  bool ParsePublicGroup(xml::XmlPullParser* parser,
-                        ParsedResource* out_resource);
-  bool ParseSymbolImpl(xml::XmlPullParser* parser,
-                       ParsedResource* out_resource);
+  bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
+  bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
-  bool ParseAddResource(xml::XmlPullParser* parser,
-                        ParsedResource* out_resource);
+  bool ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
-  bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
-                     bool weak);
+  bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
   Maybe<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
                                                const android::StringPiece& tag);
   bool ParseStyle(const ResourceType type, xml::XmlPullParser* parser,
                   ParsedResource* out_resource);
   bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
-  bool ParseDeclareStyleable(xml::XmlPullParser* parser,
-                             ParsedResource* out_resource);
+  bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource);
-  bool ParseIntegerArray(xml::XmlPullParser* parser,
-                         ParsedResource* out_resource);
-  bool ParseStringArray(xml::XmlPullParser* parser,
-                        ParsedResource* out_resource);
-  bool ParseArrayImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
-                      uint32_t typeMask);
+  bool ParseIntegerArray(xml::XmlPullParser* parser, ParsedResource* out_resource);
+  bool ParseStringArray(xml::XmlPullParser* parser, ParsedResource* out_resource);
+  bool ParseArrayImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, uint32_t typeMask);
   bool ParsePlural(xml::XmlPullParser* parser, ParsedResource* out_resource);
 
   IDiagnostics* diag_;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index e60ef66..5352ca8 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -25,19 +25,20 @@
 #include "test/Test.h"
 #include "xml/XmlPullParser.h"
 
+using ::aapt::test::ValueEq;
 using ::android::StringPiece;
 using ::testing::Eq;
 using ::testing::NotNull;
+using ::testing::Pointee;
 
 namespace aapt {
 
-constexpr const char* kXmlPreamble =
-    "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
 
 TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   std::stringstream input(kXmlPreamble);
-  input << "<attr name=\"foo\"/>" << std::endl;
+  input << R"(<attr name="foo"/>)" << std::endl;
   ResourceTable table;
   ResourceParser parser(context->GetDiagnostics(), &table, Source{"test"}, {});
   xml::XmlPullParser xml_parser(input);
@@ -46,19 +47,20 @@
 
 class ResourceParserTest : public ::testing::Test {
  public:
-  void SetUp() override { context_ = test::ContextBuilder().Build(); }
+  void SetUp() override {
+    context_ = test::ContextBuilder().Build();
+  }
 
   ::testing::AssertionResult TestParse(const StringPiece& str) {
     return TestParse(str, ConfigDescription{});
   }
 
-  ::testing::AssertionResult TestParse(const StringPiece& str,
-                                       const ConfigDescription& config) {
+  ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) {
     std::stringstream input(kXmlPreamble);
     input << "<resources>\n" << str << "\n</resources>" << std::endl;
     ResourceParserOptions parserOptions;
-    ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"},
-                          config, parserOptions);
+    ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"}, config,
+                          parserOptions);
     xml::XmlPullParser xmlParser(input);
     if (parser.Parse(&xmlParser)) {
       return ::testing::AssertionSuccess();
@@ -205,18 +207,18 @@
   // a non-existing value, and this causes problems in styles when trying to
   // resolve an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE
   // with a data value of 0.
-  BinaryPrimitive* integer = test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
-  ASSERT_NE(nullptr, integer);
-  EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType);
-  EXPECT_EQ(0u, integer->value.data);
+  Reference* null_ref = test::GetValue<Reference>(&table_, "integer/foo");
+  ASSERT_THAT(null_ref, NotNull());
+  EXPECT_FALSE(null_ref->name);
+  EXPECT_FALSE(null_ref->id);
+  EXPECT_EQ(Reference::Type::kResource, null_ref->reference_type);
 }
 
 TEST_F(ResourceParserTest, ParseEmpty) {
   std::string input = "<integer name=\"foo\">@empty</integer>";
   ASSERT_TRUE(TestParse(input));
 
-  BinaryPrimitive* integer =
-      test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
+  BinaryPrimitive* integer = test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
   ASSERT_NE(nullptr, integer);
   EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
   EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
@@ -241,22 +243,18 @@
 // ultimately
 // stored them with the default configuration. Check that we have the same
 // behavior.
-TEST_F(ResourceParserTest,
-       ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
+TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
   const ConfigDescription watch_config = test::ParseConfigOrDie("watch");
-  std::string input = R"EOF(
-        <attr name="foo" />
-        <declare-styleable name="bar">
-          <attr name="baz" />
-        </declare-styleable>)EOF";
+  std::string input = R"(
+      <attr name="foo" />
+      <declare-styleable name="bar">
+        <attr name="baz" />
+      </declare-styleable>)";
   ASSERT_TRUE(TestParse(input, watch_config));
 
-  EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/foo",
-                                                        watch_config));
-  EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/baz",
-                                                        watch_config));
-  EXPECT_EQ(nullptr, test::GetValueForConfig<Styleable>(
-                         &table_, "styleable/bar", watch_config));
+  EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/foo", watch_config));
+  EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/baz", watch_config));
+  EXPECT_EQ(nullptr, test::GetValueForConfig<Styleable>(&table_, "styleable/bar", watch_config));
 
   EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/foo"));
   EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/baz"));
@@ -833,4 +831,16 @@
   EXPECT_NE(nullptr, ValueCast<RawString>(val->entries[0].value.get()));
 }
 
+TEST_F(ResourceParserTest, ParseElementWithNoValue) {
+  std::string input = R"(
+      <item type="drawable" format="reference" name="foo" />
+      <string name="foo" />)";
+  ASSERT_TRUE(TestParse(input));
+  ASSERT_THAT(test::GetValue(&table_, "drawable/foo"), Pointee(ValueEq(Reference())));
+
+  String* str = test::GetValue<String>(&table_, "string/foo");
+  ASSERT_THAT(str, NotNull());
+  EXPECT_THAT(*str->value, Eq(""));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 818c8ce..deeef6e 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -305,21 +305,25 @@
   return {};
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const StringPiece& str) {
-  StringPiece trimmed_str(util::TrimWhitespace(str));
-  android::Res_value value = {};
+std::unique_ptr<Item> TryParseNullOrEmpty(const StringPiece& str) {
+  const StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str == "@null") {
-    // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
-    // Instead we set the data type to TYPE_REFERENCE with a value of 0.
-    value.dataType = android::Res_value::TYPE_REFERENCE;
+    return MakeNull();
   } else if (trimmed_str == "@empty") {
-    // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
-    value.dataType = android::Res_value::TYPE_NULL;
-    value.data = android::Res_value::DATA_NULL_EMPTY;
-  } else {
-    return {};
+    return MakeEmpty();
   }
-  return util::make_unique<BinaryPrimitive>(value);
+  return {};
+}
+
+std::unique_ptr<Reference> MakeNull() {
+  // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
+  // Instead we set the data type to TYPE_REFERENCE with a value of 0.
+  return util::make_unique<Reference>();
+}
+
+std::unique_ptr<BinaryPrimitive> MakeEmpty() {
+  return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_NULL,
+                                            android::Res_value::DATA_NULL_EMPTY);
 }
 
 std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
@@ -569,13 +573,15 @@
 std::unique_ptr<Item> TryParseItemForAttribute(
     const StringPiece& value, uint32_t type_mask,
     const std::function<void(const ResourceName&)>& on_create_reference) {
-  std::unique_ptr<BinaryPrimitive> null_or_empty = TryParseNullOrEmpty(value);
+  using android::ResTable_map;
+
+  auto null_or_empty = TryParseNullOrEmpty(value);
   if (null_or_empty) {
-    return std::move(null_or_empty);
+    return null_or_empty;
   }
 
   bool create = false;
-  std::unique_ptr<Reference> reference = TryParseReference(value, &create);
+  auto reference = TryParseReference(value, &create);
   if (reference) {
     if (create && on_create_reference) {
       on_create_reference(reference->name.value());
@@ -583,39 +589,37 @@
     return std::move(reference);
   }
 
-  if (type_mask & android::ResTable_map::TYPE_COLOR) {
+  if (type_mask & ResTable_map::TYPE_COLOR) {
     // Try parsing this as a color.
-    std::unique_ptr<BinaryPrimitive> color = TryParseColor(value);
+    auto color = TryParseColor(value);
     if (color) {
       return std::move(color);
     }
   }
 
-  if (type_mask & android::ResTable_map::TYPE_BOOLEAN) {
+  if (type_mask & ResTable_map::TYPE_BOOLEAN) {
     // Try parsing this as a boolean.
-    std::unique_ptr<BinaryPrimitive> boolean = TryParseBool(value);
+    auto boolean = TryParseBool(value);
     if (boolean) {
       return std::move(boolean);
     }
   }
 
-  if (type_mask & android::ResTable_map::TYPE_INTEGER) {
+  if (type_mask & ResTable_map::TYPE_INTEGER) {
     // Try parsing this as an integer.
-    std::unique_ptr<BinaryPrimitive> integer = TryParseInt(value);
+    auto integer = TryParseInt(value);
     if (integer) {
       return std::move(integer);
     }
   }
 
-  const uint32_t float_mask = android::ResTable_map::TYPE_FLOAT |
-                              android::ResTable_map::TYPE_DIMENSION |
-                              android::ResTable_map::TYPE_FRACTION;
+  const uint32_t float_mask =
+      ResTable_map::TYPE_FLOAT | ResTable_map::TYPE_DIMENSION | ResTable_map::TYPE_FRACTION;
   if (type_mask & float_mask) {
     // Try parsing this as a float.
-    std::unique_ptr<BinaryPrimitive> floating_point = TryParseFloat(value);
+    auto floating_point = TryParseFloat(value);
     if (floating_point) {
-      if (type_mask &
-          AndroidTypeToAttributeTypeMask(floating_point->value.dataType)) {
+      if (type_mask & AndroidTypeToAttributeTypeMask(floating_point->value.dataType)) {
         return std::move(floating_point);
       }
     }
@@ -630,24 +634,25 @@
 std::unique_ptr<Item> TryParseItemForAttribute(
     const StringPiece& str, const Attribute* attr,
     const std::function<void(const ResourceName&)>& on_create_reference) {
+  using android::ResTable_map;
+
   const uint32_t type_mask = attr->type_mask;
-  std::unique_ptr<Item> value =
-      TryParseItemForAttribute(str, type_mask, on_create_reference);
+  auto value = TryParseItemForAttribute(str, type_mask, on_create_reference);
   if (value) {
     return value;
   }
 
-  if (type_mask & android::ResTable_map::TYPE_ENUM) {
+  if (type_mask & ResTable_map::TYPE_ENUM) {
     // Try parsing this as an enum.
-    std::unique_ptr<BinaryPrimitive> enum_value = TryParseEnumSymbol(attr, str);
+    auto enum_value = TryParseEnumSymbol(attr, str);
     if (enum_value) {
       return std::move(enum_value);
     }
   }
 
-  if (type_mask & android::ResTable_map::TYPE_FLAGS) {
+  if (type_mask & ResTable_map::TYPE_FLAGS) {
     // Try parsing this as a flag.
-    std::unique_ptr<BinaryPrimitive> flag_value = TryParseFlagSymbol(attr, str);
+    auto flag_value = TryParseFlagSymbol(attr, str);
     if (flag_value) {
       return std::move(flag_value);
     }
@@ -655,8 +660,7 @@
   return {};
 }
 
-std::string BuildResourceFileName(const ResourceFile& res_file,
-                                  const NameMangler* mangler) {
+std::string BuildResourceFileName(const ResourceFile& res_file, const NameMangler* mangler) {
   std::stringstream out;
   out << "res/" << res_file.name.type;
   if (res_file.config != ConfigDescription{}) {
@@ -719,9 +723,9 @@
         ref_type = Reference::Type::kAttribute;
       }
 
-      if (data == 0) {
+      if (data == 0u) {
         // A reference of 0, must be the magic @null reference.
-        return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_REFERENCE, 0u);
+        return util::make_unique<Reference>();
       }
 
       // This is a normal reference.
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index da0fc8e..36f6c2b 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -133,7 +133,16 @@
  * Returns a BinaryPrimitve object representing @null or @empty if the string
  * was parsed as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const android::StringPiece& str);
+std::unique_ptr<Item> TryParseNullOrEmpty(const android::StringPiece& str);
+
+// Returns a Reference representing @null.
+// Due to runtime compatibility issues, this is encoded as a reference with ID 0.
+// The runtime will convert this to TYPE_NULL.
+std::unique_ptr<Reference> MakeNull();
+
+// Returns a BinaryPrimitive representing @empty. This is encoded as a Res_value with
+// type Res_value::TYPE_NULL and data Res_value::DATA_NULL_EMPTY.
+std::unique_ptr<BinaryPrimitive> MakeEmpty();
 
 /*
  * Returns a BinaryPrimitve object representing a color if the string was parsed
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 048c692..cdc34f1 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -19,6 +19,9 @@
 #include "Resource.h"
 #include "test/Test.h"
 
+using ::aapt::test::ValueEq;
+using ::testing::Pointee;
+
 namespace aapt {
 
 TEST(ResourceUtilsTest, ParseBool) {
@@ -200,4 +203,22 @@
   EXPECT_EQ(0u, result->value.data);
 }
 
+TEST(ResourceUtilsTest, NullIsEmptyReference) {
+  auto null_value = ResourceUtils::MakeNull();
+  ASSERT_THAT(null_value, Pointee(ValueEq(Reference())));
+
+  auto value = ResourceUtils::TryParseNullOrEmpty("@null");
+  ASSERT_THAT(value, Pointee(ValueEq(Reference())));
+}
+
+TEST(ResourceUtilsTest, EmptyIsBinaryPrimitive) {
+  auto empty_value = ResourceUtils::MakeEmpty();
+  ASSERT_THAT(empty_value, Pointee(ValueEq(BinaryPrimitive(android::Res_value::TYPE_NULL,
+                                                           android::Res_value::DATA_NULL_EMPTY))));
+
+  auto value = ResourceUtils::TryParseNullOrEmpty("@empty");
+  ASSERT_THAT(value, Pointee(ValueEq(BinaryPrimitive(android::Res_value::TYPE_NULL,
+                                                     android::Res_value::DATA_NULL_EMPTY))));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index abfdec4..e808984 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -94,8 +94,8 @@
 
 bool Reference::Flatten(android::Res_value* out_value) const {
   const ResourceId resid = id.value_or_default(ResourceId(0));
-  const bool dynamic =
-      (resid.package_id() != kFrameworkPackageId && resid.package_id() != kAppPackageId);
+  const bool dynamic = resid.is_valid_dynamic() && resid.package_id() != kFrameworkPackageId &&
+                       resid.package_id() != kAppPackageId;
 
   if (reference_type == Reference::Type::kResource) {
     if (dynamic) {
@@ -119,22 +119,29 @@
 }
 
 void Reference::Print(std::ostream* out) const {
-  *out << "(reference) ";
-  if (reference_type == Reference::Type::kResource) {
-    *out << "@";
-    if (private_reference) {
-      *out << "*";
+  if (reference_type == Type::kResource) {
+    *out << "(reference) @";
+    if (!name && !id) {
+      *out << "null";
+      return;
     }
   } else {
-    *out << "?";
+    *out << "(attr-reference) ?";
+  }
+
+  if (private_reference) {
+    *out << "*";
   }
 
   if (name) {
     *out << name.value();
   }
 
-  if (id && !Res_INTERNALID(id.value().id)) {
-    *out << " " << id.value();
+  if (id && id.value().is_valid_dynamic()) {
+    if (name) {
+      *out << " ";
+    }
+    *out << id.value();
   }
 }
 
@@ -314,7 +321,11 @@
 void BinaryPrimitive::Print(std::ostream* out) const {
   switch (value.dataType) {
     case android::Res_value::TYPE_NULL:
-      *out << "(null)";
+      if (value.data == android::Res_value::DATA_NULL_EMPTY) {
+        *out << "(empty)";
+      } else {
+        *out << "(null)";
+      }
       break;
     case android::Res_value::TYPE_INT_DEC:
       *out << "(integer) " << static_cast<int32_t>(value.data);
diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp
index e154d93..69f8e67 100644
--- a/tools/aapt2/ResourceValues_test.cpp
+++ b/tools/aapt2/ResourceValues_test.cpp
@@ -180,4 +180,14 @@
   EXPECT_TRUE(a->Equals(expected.get()));
 }
 
+// TYPE_NULL is encoded as TYPE_REFERENCE with a value of 0. This is represented in AAPT2
+// by a default constructed Reference value.
+TEST(ResourcesValuesTest, EmptyReferenceFlattens) {
+  android::Res_value value = {};
+  ASSERT_TRUE(Reference().Flatten(&value));
+
+  EXPECT_EQ(android::Res_value::TYPE_REFERENCE, value.dataType);
+  EXPECT_EQ(0x0u, value.data);
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/integration-tests/AppOne/res/values/test.xml b/tools/aapt2/integration-tests/AppOne/res/values/test.xml
index 5104f82..2c9e8b8 100644
--- a/tools/aapt2/integration-tests/AppOne/res/values/test.xml
+++ b/tools/aapt2/integration-tests/AppOne/res/values/test.xml
@@ -38,4 +38,7 @@
         <attr name="android:text" />
         <attr name="layout_width" />
     </declare-styleable>
+
+    <!-- Declare an empty, resource -->
+    <item type="drawable" name="undefined" />
 </resources>
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 18498e3..a8e510c 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -296,7 +296,10 @@
                                     IAaptContext* context, SymbolTable* symbols,
                                     xml::IPackageDeclStack* decls) {
   CHECK(reference != nullptr);
-  CHECK(reference->name || reference->id);
+  if (!reference->name && !reference->id) {
+    // This is @null.
+    return true;
+  }
 
   Reference transformed_reference = *reference;
   TransformReferenceFromNamespace(decls, context->GetCompilationPackage(), &transformed_reference);
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index 1d0041b..4b56192 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -343,26 +343,19 @@
     return value;
   }
 
-  bool DeserializeReferenceFromPb(const pb::Reference& pb_ref,
-                                  Reference* out_ref) {
+  bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* out_ref) {
     out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type());
     out_ref->private_reference = pb_ref.private_();
 
-    if (!pb_ref.has_id() && !pb_ref.has_symbol_idx()) {
-      return false;
-    }
-
     if (pb_ref.has_id()) {
       out_ref->id = ResourceId(pb_ref.id());
     }
 
     if (pb_ref.has_symbol_idx()) {
-      const std::string str_symbol =
-          util::GetString(*symbol_pool_, pb_ref.symbol_idx());
+      const std::string str_symbol = util::GetString(*symbol_pool_, pb_ref.symbol_idx());
       ResourceNameRef name_ref;
       if (!ResourceUtils::ParseResourceName(str_symbol, &name_ref, nullptr)) {
-        diag_->Error(DiagMessage(source_) << "invalid reference name '"
-                                          << str_symbol << "'");
+        diag_->Error(DiagMessage(source_) << "invalid reference name '" << str_symbol << "'");
         return false;
       }
 
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
index de10bb8..d87d64e 100644
--- a/tools/aapt2/proto/TableProtoSerializer.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -190,8 +190,7 @@
     }
 
     if (ref.name) {
-      StringPool::Ref symbol_ref =
-          symbol_pool_->MakeRef(ref.name.value().ToString());
+      StringPool::Ref symbol_ref = symbol_pool_->MakeRef(ref.name.value().ToString());
       pb_ref->set_symbol_idx(static_cast<uint32_t>(symbol_ref.index()));
     }
 
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 02acedb..f3da780 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -59,8 +59,7 @@
 
   ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id,
                                      const android::StringPiece& ref) {
-    return AddValue(name, id,
-                    util::make_unique<Reference>(ParseNameOrDie(ref)));
+    return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref)));
   }
 
   ResourceTableBuilder& AddString(const android::StringPiece& name,
@@ -111,8 +110,8 @@
   ResourceTableBuilder& AddValue(const android::StringPiece& name, const ConfigDescription& config,
                                  const ResourceId& id, std::unique_ptr<Value> value) {
     ResourceName res_name = ParseNameOrDie(name);
-    CHECK(table_->AddResourceAllowMangled(res_name, id, config, {},
-                                          std::move(value), &diagnostics_));
+    CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value),
+                                          GetDiagnostics()));
     return *this;
   }
 
@@ -122,7 +121,7 @@
     Symbol symbol;
     symbol.state = state;
     symbol.allow_new = allow_new;
-    CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, &diagnostics_));
+    CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics()));
     return *this;
   }
 
@@ -131,7 +130,6 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceTableBuilder);
 
-  DummyDiagnosticsImpl diagnostics_;
   std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>();
 };
 
diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp
new file mode 100644
index 0000000..0fabbc4
--- /dev/null
+++ b/tools/aapt2/test/Common.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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 "test/Common.h"
+
+namespace aapt {
+namespace test {
+
+struct DummyDiagnosticsImpl : public IDiagnostics {
+  void Log(Level level, DiagMessageActual& actual_msg) override {
+    switch (level) {
+      case Level::Note:
+        return;
+
+      case Level::Warn:
+        std::cerr << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl;
+        break;
+
+      case Level::Error:
+        std::cerr << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl;
+        break;
+    }
+  }
+};
+
+IDiagnostics* GetDiagnostics() {
+  static DummyDiagnosticsImpl diag;
+  return &diag;
+}
+
+template <>
+Value* GetValueForConfigAndProduct<Value>(ResourceTable* table,
+                                          const android::StringPiece& res_name,
+                                          const ConfigDescription& config,
+                                          const android::StringPiece& product) {
+  Maybe<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name));
+  if (result) {
+    ResourceConfigValue* config_value = result.value().entry->FindValue(config, product);
+    if (config_value) {
+      return config_value->value.get();
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace test
+}  // namespace aapt
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index a937de8..0585148 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -46,27 +46,7 @@
 namespace aapt {
 namespace test {
 
-struct DummyDiagnosticsImpl : public IDiagnostics {
-  void Log(Level level, DiagMessageActual& actual_msg) override {
-    switch (level) {
-      case Level::Note:
-        return;
-
-      case Level::Warn:
-        std::cerr << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl;
-        break;
-
-      case Level::Error:
-        std::cerr << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl;
-        break;
-    }
-  }
-};
-
-inline IDiagnostics* GetDiagnostics() {
-  static DummyDiagnosticsImpl diag;
-  return &diag;
-}
+IDiagnostics* GetDiagnostics();
 
 inline ResourceName ParseNameOrDie(const android::StringPiece& str) {
   ResourceNameRef ref;
@@ -80,7 +60,7 @@
   return config;
 }
 
-template <typename T>
+template <typename T = Value>
 T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& res_name,
                                const ConfigDescription& config,
                                const android::StringPiece& product) {
@@ -94,13 +74,19 @@
   return nullptr;
 }
 
-template <typename T>
+template <>
+Value* GetValueForConfigAndProduct<Value>(ResourceTable* table,
+                                          const android::StringPiece& res_name,
+                                          const ConfigDescription& config,
+                                          const android::StringPiece& product);
+
+template <typename T = Value>
 T* GetValueForConfig(ResourceTable* table, const android::StringPiece& res_name,
                      const ConfigDescription& config) {
   return GetValueForConfigAndProduct<T>(table, res_name, config, {});
 }
 
-template <typename T>
+template <typename T = Value>
 T* GetValue(ResourceTable* table, const android::StringPiece& res_name) {
   return GetValueForConfig<T>(table, res_name, {});
 }