Loading in an APk changed the ordering of strings in the string pool. When loading an apk, assign the strings to the same index as they are in the ResStringPool. Bug: 118831219 Test: "aapt2 dump strings left.apk" prints in the correct order, "aapt2 convert left.apk --output-format binary -o left_binary.apk" has entries in the correct order, and aapt2_tests Change-Id: I00014c02195f39c1152a110e90083d9b14e9216e
524 lines
16 KiB
C++
524 lines
16 KiB
C++
/*
|
|
* Copyright (C) 2015 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 "StringPool.h"
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
#include "android-base/logging.h"
|
|
#include "androidfw/ResourceTypes.h"
|
|
#include "androidfw/StringPiece.h"
|
|
|
|
#include "util/BigBuffer.h"
|
|
#include "util/Util.h"
|
|
|
|
using ::android::StringPiece;
|
|
|
|
namespace aapt {
|
|
|
|
StringPool::Ref::Ref() : entry_(nullptr) {}
|
|
|
|
StringPool::Ref::Ref(const StringPool::Ref& rhs) : entry_(rhs.entry_) {
|
|
if (entry_ != nullptr) {
|
|
entry_->ref_++;
|
|
}
|
|
}
|
|
|
|
StringPool::Ref::Ref(StringPool::Entry* entry) : entry_(entry) {
|
|
if (entry_ != nullptr) {
|
|
entry_->ref_++;
|
|
}
|
|
}
|
|
|
|
StringPool::Ref::~Ref() {
|
|
if (entry_ != nullptr) {
|
|
entry_->ref_--;
|
|
}
|
|
}
|
|
|
|
StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) {
|
|
if (rhs.entry_ != nullptr) {
|
|
rhs.entry_->ref_++;
|
|
}
|
|
|
|
if (entry_ != nullptr) {
|
|
entry_->ref_--;
|
|
}
|
|
entry_ = rhs.entry_;
|
|
return *this;
|
|
}
|
|
|
|
bool StringPool::Ref::operator==(const Ref& rhs) const {
|
|
return entry_->value == rhs.entry_->value;
|
|
}
|
|
|
|
bool StringPool::Ref::operator!=(const Ref& rhs) const {
|
|
return entry_->value != rhs.entry_->value;
|
|
}
|
|
|
|
const std::string* StringPool::Ref::operator->() const {
|
|
return &entry_->value;
|
|
}
|
|
|
|
const std::string& StringPool::Ref::operator*() const {
|
|
return entry_->value;
|
|
}
|
|
|
|
size_t StringPool::Ref::index() const {
|
|
// Account for the styles, which *always* come first.
|
|
return entry_->pool_->styles_.size() + entry_->index_;
|
|
}
|
|
|
|
const StringPool::Context& StringPool::Ref::GetContext() const {
|
|
return entry_->context;
|
|
}
|
|
|
|
StringPool::StyleRef::StyleRef() : entry_(nullptr) {}
|
|
|
|
StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs)
|
|
: entry_(rhs.entry_) {
|
|
if (entry_ != nullptr) {
|
|
entry_->ref_++;
|
|
}
|
|
}
|
|
|
|
StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : entry_(entry) {
|
|
if (entry_ != nullptr) {
|
|
entry_->ref_++;
|
|
}
|
|
}
|
|
|
|
StringPool::StyleRef::~StyleRef() {
|
|
if (entry_ != nullptr) {
|
|
entry_->ref_--;
|
|
}
|
|
}
|
|
|
|
StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) {
|
|
if (rhs.entry_ != nullptr) {
|
|
rhs.entry_->ref_++;
|
|
}
|
|
|
|
if (entry_ != nullptr) {
|
|
entry_->ref_--;
|
|
}
|
|
entry_ = rhs.entry_;
|
|
return *this;
|
|
}
|
|
|
|
bool StringPool::StyleRef::operator==(const StyleRef& rhs) const {
|
|
if (entry_->value != rhs.entry_->value) {
|
|
return false;
|
|
}
|
|
|
|
if (entry_->spans.size() != rhs.entry_->spans.size()) {
|
|
return false;
|
|
}
|
|
|
|
auto rhs_iter = rhs.entry_->spans.begin();
|
|
for (const Span& span : entry_->spans) {
|
|
const Span& rhs_span = *rhs_iter;
|
|
if (span.first_char != rhs_span.first_char || span.last_char != rhs_span.last_char ||
|
|
span.name != rhs_span.name) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool StringPool::StyleRef::operator!=(const StyleRef& rhs) const {
|
|
return !operator==(rhs);
|
|
}
|
|
|
|
const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
|
|
return entry_;
|
|
}
|
|
|
|
const StringPool::StyleEntry& StringPool::StyleRef::operator*() const {
|
|
return *entry_;
|
|
}
|
|
|
|
size_t StringPool::StyleRef::index() const {
|
|
return entry_->index_;
|
|
}
|
|
|
|
const StringPool::Context& StringPool::StyleRef::GetContext() const {
|
|
return entry_->context;
|
|
}
|
|
|
|
StringPool::Ref StringPool::MakeRef(const StringPiece& str) {
|
|
return MakeRefImpl(str, Context{}, true);
|
|
}
|
|
|
|
StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context,
|
|
Maybe<size_t> index) {
|
|
return MakeRefImpl(str, context, true, index);
|
|
}
|
|
|
|
StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context,
|
|
bool unique, Maybe<size_t> index) {
|
|
if (unique) {
|
|
auto range = indexed_strings_.equal_range(str);
|
|
for (auto iter = range.first; iter != range.second; ++iter) {
|
|
if (context.priority == iter->second->context.priority) {
|
|
return Ref(iter->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
const size_t size = strings_.size();
|
|
// Insert the string at the end of the string vector if no index is specified
|
|
const size_t insertion_index = index ? index.value() : size;
|
|
|
|
std::unique_ptr<Entry> entry(new Entry());
|
|
entry->value = str.to_string();
|
|
entry->context = context;
|
|
entry->index_ = insertion_index;
|
|
entry->ref_ = 0;
|
|
entry->pool_ = this;
|
|
|
|
Entry* borrow = entry.get();
|
|
if (insertion_index == size) {
|
|
strings_.emplace_back(std::move(entry));
|
|
} else {
|
|
// Allocate enough space for the string at the index
|
|
strings_.resize(std::max(insertion_index + 1, size));
|
|
strings_[insertion_index] = std::move(entry);
|
|
}
|
|
|
|
indexed_strings_.insert(std::make_pair(StringPiece(borrow->value), borrow));
|
|
return Ref(borrow);
|
|
}
|
|
|
|
StringPool::Ref StringPool::MakeRef(const Ref& ref) {
|
|
if (ref.entry_->pool_ == this) {
|
|
return ref;
|
|
}
|
|
return MakeRef(ref.entry_->value, ref.entry_->context);
|
|
}
|
|
|
|
StringPool::StyleRef StringPool::MakeRef(const StyleString& str) {
|
|
return MakeRef(str, Context{});
|
|
}
|
|
|
|
StringPool::StyleRef StringPool::MakeRef(const StyleString& str, const Context& context) {
|
|
std::unique_ptr<StyleEntry> entry(new StyleEntry());
|
|
entry->value = str.str;
|
|
entry->context = context;
|
|
entry->index_ = styles_.size();
|
|
entry->ref_ = 0;
|
|
for (const aapt::Span& span : str.spans) {
|
|
entry->spans.emplace_back(Span{MakeRef(span.name), span.first_char, span.last_char});
|
|
}
|
|
|
|
StyleEntry* borrow = entry.get();
|
|
styles_.emplace_back(std::move(entry));
|
|
return StyleRef(borrow);
|
|
}
|
|
|
|
StringPool::StyleRef StringPool::MakeRef(const StyleRef& ref) {
|
|
std::unique_ptr<StyleEntry> entry(new StyleEntry());
|
|
entry->value = ref.entry_->value;
|
|
entry->context = ref.entry_->context;
|
|
entry->index_ = styles_.size();
|
|
entry->ref_ = 0;
|
|
for (const Span& span : ref.entry_->spans) {
|
|
entry->spans.emplace_back(Span{MakeRef(*span.name), span.first_char, span.last_char});
|
|
}
|
|
|
|
StyleEntry* borrow = entry.get();
|
|
styles_.emplace_back(std::move(entry));
|
|
return StyleRef(borrow);
|
|
}
|
|
|
|
void StringPool::ReAssignIndices() {
|
|
// Assign the style indices.
|
|
const size_t style_len = styles_.size();
|
|
for (size_t index = 0; index < style_len; index++) {
|
|
styles_[index]->index_ = index;
|
|
}
|
|
|
|
// Assign the string indices.
|
|
const size_t string_len = strings_.size();
|
|
for (size_t index = 0; index < string_len; index++) {
|
|
strings_[index]->index_ = index;
|
|
}
|
|
}
|
|
|
|
void StringPool::Merge(StringPool&& pool) {
|
|
// First, change the owning pool for the incoming strings.
|
|
for (std::unique_ptr<Entry>& entry : pool.strings_) {
|
|
entry->pool_ = this;
|
|
}
|
|
|
|
// Now move the styles, strings, and indices over.
|
|
std::move(pool.styles_.begin(), pool.styles_.end(), std::back_inserter(styles_));
|
|
pool.styles_.clear();
|
|
std::move(pool.strings_.begin(), pool.strings_.end(), std::back_inserter(strings_));
|
|
pool.strings_.clear();
|
|
indexed_strings_.insert(pool.indexed_strings_.begin(), pool.indexed_strings_.end());
|
|
pool.indexed_strings_.clear();
|
|
|
|
ReAssignIndices();
|
|
}
|
|
|
|
void StringPool::HintWillAdd(size_t string_count, size_t style_count) {
|
|
strings_.reserve(strings_.size() + string_count);
|
|
styles_.reserve(styles_.size() + style_count);
|
|
}
|
|
|
|
void StringPool::Prune() {
|
|
const auto iter_end = indexed_strings_.end();
|
|
auto index_iter = indexed_strings_.begin();
|
|
while (index_iter != iter_end) {
|
|
if (index_iter->second->ref_ <= 0) {
|
|
index_iter = indexed_strings_.erase(index_iter);
|
|
} else {
|
|
++index_iter;
|
|
}
|
|
}
|
|
|
|
auto end_iter2 =
|
|
std::remove_if(strings_.begin(), strings_.end(),
|
|
[](const std::unique_ptr<Entry>& entry) -> bool { return entry->ref_ <= 0; });
|
|
auto end_iter3 = std::remove_if(
|
|
styles_.begin(), styles_.end(),
|
|
[](const std::unique_ptr<StyleEntry>& entry) -> bool { return entry->ref_ <= 0; });
|
|
|
|
// Remove the entries at the end or else we'll be accessing a deleted string from the StyleEntry.
|
|
strings_.erase(end_iter2, strings_.end());
|
|
styles_.erase(end_iter3, styles_.end());
|
|
|
|
ReAssignIndices();
|
|
}
|
|
|
|
template <typename E>
|
|
static void SortEntries(
|
|
std::vector<std::unique_ptr<E>>& entries,
|
|
const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) {
|
|
using UEntry = std::unique_ptr<E>;
|
|
|
|
if (cmp != nullptr) {
|
|
std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool {
|
|
int r = cmp(a->context, b->context);
|
|
if (r == 0) {
|
|
r = a->value.compare(b->value);
|
|
}
|
|
return r < 0;
|
|
});
|
|
} else {
|
|
std::sort(entries.begin(), entries.end(),
|
|
[](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; });
|
|
}
|
|
}
|
|
|
|
void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) {
|
|
SortEntries(styles_, cmp);
|
|
SortEntries(strings_, cmp);
|
|
ReAssignIndices();
|
|
}
|
|
|
|
template <typename T>
|
|
static T* EncodeLength(T* data, size_t length) {
|
|
static_assert(std::is_integral<T>::value, "wat.");
|
|
|
|
constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
|
|
constexpr size_t kMaxSize = kMask - 1;
|
|
if (length > kMaxSize) {
|
|
*data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8)));
|
|
}
|
|
*data++ = length;
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum possible string length that can be successfully encoded
|
|
* using 2 units of the specified T.
|
|
* EncodeLengthMax<char> -> maximum unit length of 0x7FFF
|
|
* EncodeLengthMax<char16_t> -> maximum unit length of 0x7FFFFFFF
|
|
**/
|
|
template <typename T>
|
|
static size_t EncodeLengthMax() {
|
|
static_assert(std::is_integral<T>::value, "wat.");
|
|
|
|
constexpr size_t kMask = 1 << ((sizeof(T) * 8 * 2) - 1);
|
|
constexpr size_t max = kMask - 1;
|
|
return max;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of units (1 or 2) needed to encode the string length
|
|
* before writing the string.
|
|
*/
|
|
template <typename T>
|
|
static size_t EncodedLengthUnits(size_t length) {
|
|
static_assert(std::is_integral<T>::value, "wat.");
|
|
|
|
constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
|
|
constexpr size_t kMaxSize = kMask - 1;
|
|
return length > kMaxSize ? 2 : 1;
|
|
}
|
|
|
|
const std::string kStringTooLarge = "STRING_TOO_LARGE";
|
|
|
|
static bool EncodeString(const std::string& str, const bool utf8, BigBuffer* out,
|
|
IDiagnostics* diag) {
|
|
if (utf8) {
|
|
const std::string& encoded = util::Utf8ToModifiedUtf8(str);
|
|
const ssize_t utf16_length = utf8_to_utf16_length(
|
|
reinterpret_cast<const uint8_t*>(encoded.data()), encoded.size());
|
|
CHECK(utf16_length >= 0);
|
|
|
|
// Make sure the lengths to be encoded do not exceed the maximum length that
|
|
// can be encoded using chars
|
|
if ((((size_t)encoded.size()) > EncodeLengthMax<char>())
|
|
|| (((size_t)utf16_length) > EncodeLengthMax<char>())) {
|
|
|
|
diag->Error(DiagMessage() << "string too large to encode using UTF-8 "
|
|
<< "written instead as '" << kStringTooLarge << "'");
|
|
|
|
EncodeString(kStringTooLarge, utf8, out, diag);
|
|
return false;
|
|
}
|
|
|
|
const size_t total_size = EncodedLengthUnits<char>(utf16_length)
|
|
+ EncodedLengthUnits<char>(encoded.size()) + encoded.size() + 1;
|
|
|
|
char* data = out->NextBlock<char>(total_size);
|
|
|
|
// First encode the UTF16 string length.
|
|
data = EncodeLength(data, utf16_length);
|
|
|
|
// Now encode the size of the real UTF8 string.
|
|
data = EncodeLength(data, encoded.size());
|
|
strncpy(data, encoded.data(), encoded.size());
|
|
|
|
} else {
|
|
const std::u16string encoded = util::Utf8ToUtf16(str);
|
|
const ssize_t utf16_length = encoded.size();
|
|
|
|
// Make sure the length to be encoded does not exceed the maximum possible
|
|
// length that can be encoded
|
|
if (((size_t)utf16_length) > EncodeLengthMax<char16_t>()) {
|
|
diag->Error(DiagMessage() << "string too large to encode using UTF-16 "
|
|
<< "written instead as '" << kStringTooLarge << "'");
|
|
|
|
EncodeString(kStringTooLarge, utf8, out, diag);
|
|
return false;
|
|
}
|
|
|
|
// Total number of 16-bit words to write.
|
|
const size_t total_size = EncodedLengthUnits<char16_t>(utf16_length)
|
|
+ encoded.size() + 1;
|
|
|
|
char16_t* data = out->NextBlock<char16_t>(total_size);
|
|
|
|
// Encode the actual UTF16 string length.
|
|
data = EncodeLength(data, utf16_length);
|
|
const size_t byte_length = encoded.size() * sizeof(char16_t);
|
|
|
|
// NOTE: For some reason, strncpy16(data, entry->value.data(),
|
|
// entry->value.size()) truncates the string.
|
|
memcpy(data, encoded.data(), byte_length);
|
|
|
|
// The null-terminating character is already here due to the block of data
|
|
// being set to 0s on allocation.
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8,
|
|
IDiagnostics* diag) {
|
|
bool no_error = true;
|
|
const size_t start_index = out->size();
|
|
android::ResStringPool_header* header = out->NextBlock<android::ResStringPool_header>();
|
|
header->header.type = util::HostToDevice16(android::RES_STRING_POOL_TYPE);
|
|
header->header.headerSize = util::HostToDevice16(sizeof(*header));
|
|
header->stringCount = util::HostToDevice32(pool.size());
|
|
header->styleCount = util::HostToDevice32(pool.styles_.size());
|
|
if (utf8) {
|
|
header->flags |= android::ResStringPool_header::UTF8_FLAG;
|
|
}
|
|
|
|
uint32_t* indices = pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr;
|
|
uint32_t* style_indices =
|
|
pool.styles_.size() != 0 ? out->NextBlock<uint32_t>(pool.styles_.size()) : nullptr;
|
|
|
|
const size_t before_strings_index = out->size();
|
|
header->stringsStart = before_strings_index - start_index;
|
|
|
|
// Styles always come first.
|
|
for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) {
|
|
*indices++ = out->size() - before_strings_index;
|
|
no_error = EncodeString(entry->value, utf8, out, diag) && no_error;
|
|
}
|
|
|
|
for (const std::unique_ptr<Entry>& entry : pool.strings_) {
|
|
*indices++ = out->size() - before_strings_index;
|
|
no_error = EncodeString(entry->value, utf8, out, diag) && no_error;
|
|
}
|
|
|
|
out->Align4();
|
|
|
|
if (style_indices != nullptr) {
|
|
const size_t before_styles_index = out->size();
|
|
header->stylesStart = util::HostToDevice32(before_styles_index - start_index);
|
|
|
|
for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) {
|
|
*style_indices++ = out->size() - before_styles_index;
|
|
|
|
if (!entry->spans.empty()) {
|
|
android::ResStringPool_span* span =
|
|
out->NextBlock<android::ResStringPool_span>(entry->spans.size());
|
|
for (const Span& s : entry->spans) {
|
|
span->name.index = util::HostToDevice32(s.name.index());
|
|
span->firstChar = util::HostToDevice32(s.first_char);
|
|
span->lastChar = util::HostToDevice32(s.last_char);
|
|
span++;
|
|
}
|
|
}
|
|
|
|
uint32_t* spanEnd = out->NextBlock<uint32_t>();
|
|
*spanEnd = android::ResStringPool_span::END;
|
|
}
|
|
|
|
// The error checking code in the platform looks for an entire
|
|
// ResStringPool_span structure worth of 0xFFFFFFFF at the end
|
|
// of the style block, so fill in the remaining 2 32bit words
|
|
// with 0xFFFFFFFF.
|
|
const size_t padding_length = sizeof(android::ResStringPool_span) -
|
|
sizeof(android::ResStringPool_span::name);
|
|
uint8_t* padding = out->NextBlock<uint8_t>(padding_length);
|
|
memset(padding, 0xff, padding_length);
|
|
out->Align4();
|
|
}
|
|
header->header.size = util::HostToDevice32(out->size() - start_index);
|
|
return no_error;
|
|
}
|
|
|
|
bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) {
|
|
return Flatten(out, pool, true, diag);
|
|
}
|
|
|
|
bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) {
|
|
return Flatten(out, pool, false, diag);
|
|
}
|
|
|
|
} // namespace aapt
|