lin
2025-07-31 065ea569db06206874bbfa18eb25ff6121aec09b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
 * 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 "type_lookup_table.h"
 
#include <cstring>
#include <memory>
 
#include "base/bit_utils.h"
#include "base/leb128.h"
#include "dex/dex_file-inl.h"
#include "dex/utf-inl.h"
 
namespace art {
 
static inline bool ModifiedUtf8StringEquals(const char* lhs, const char* rhs) {
  return CompareModifiedUtf8ToModifiedUtf8AsUtf16CodePointValues(lhs, rhs) == 0;
}
 
TypeLookupTable TypeLookupTable::Create(const DexFile& dex_file) {
  uint32_t num_class_defs = dex_file.NumClassDefs();
  if (UNLIKELY(!SupportedSize(num_class_defs))) {
    return TypeLookupTable();
  }
  size_t mask_bits = CalculateMaskBits(num_class_defs);
  size_t size = 1u << mask_bits;
  std::unique_ptr<Entry[]> owned_entries(new Entry[size]);
  Entry* entries = owned_entries.get();
 
  static_assert(alignof(Entry) == 4u, "Expecting Entry to be 4-byte aligned.");
  const uint32_t mask = Entry::GetMask(mask_bits);
  std::vector<uint16_t> conflict_class_defs;
  // The first stage. Put elements on their initial positions. If an initial position is already
  // occupied then delay the insertion of the element to the second stage to reduce probing
  // distance.
  for (size_t class_def_idx = 0; class_def_idx < dex_file.NumClassDefs(); ++class_def_idx) {
    const dex::ClassDef& class_def = dex_file.GetClassDef(class_def_idx);
    const dex::TypeId& type_id = dex_file.GetTypeId(class_def.class_idx_);
    const dex::StringId& str_id = dex_file.GetStringId(type_id.descriptor_idx_);
    const uint32_t hash = ComputeModifiedUtf8Hash(dex_file.GetStringData(str_id));
    const uint32_t pos = hash & mask;
    if (entries[pos].IsEmpty()) {
      entries[pos] = Entry(str_id.string_data_off_, hash, class_def_idx, mask_bits);
      DCHECK(entries[pos].IsLast(mask_bits));
    } else {
      conflict_class_defs.push_back(class_def_idx);
    }
  }
  // The second stage. The initial position of these elements had a collision. Put these elements
  // into the nearest free cells and link them together by updating next_pos_delta.
  for (uint16_t class_def_idx : conflict_class_defs) {
    const dex::ClassDef& class_def = dex_file.GetClassDef(class_def_idx);
    const dex::TypeId& type_id = dex_file.GetTypeId(class_def.class_idx_);
    const dex::StringId& str_id = dex_file.GetStringId(type_id.descriptor_idx_);
    const uint32_t hash = ComputeModifiedUtf8Hash(dex_file.GetStringData(str_id));
    // Find the last entry in the chain.
    uint32_t tail_pos = hash & mask;
    DCHECK(!entries[tail_pos].IsEmpty());
    while (!entries[tail_pos].IsLast(mask_bits)) {
      tail_pos = (tail_pos + entries[tail_pos].GetNextPosDelta(mask_bits)) & mask;
      DCHECK(!entries[tail_pos].IsEmpty());
    }
    // Find an empty entry for insertion.
    uint32_t insert_pos = tail_pos;
    do {
      insert_pos = (insert_pos + 1) & mask;
    } while (!entries[insert_pos].IsEmpty());
    // Insert and chain the new entry.
    entries[insert_pos] = Entry(str_id.string_data_off_, hash, class_def_idx, mask_bits);
    entries[tail_pos].SetNextPosDelta((insert_pos - tail_pos) & mask, mask_bits);
    DCHECK(entries[insert_pos].IsLast(mask_bits));
    DCHECK(!entries[tail_pos].IsLast(mask_bits));
  }
 
  return TypeLookupTable(dex_file.DataBegin(), mask_bits, entries, std::move(owned_entries));
}
 
TypeLookupTable TypeLookupTable::Open(const uint8_t* dex_data_pointer,
                                      const uint8_t* raw_data,
                                      uint32_t num_class_defs) {
  DCHECK_ALIGNED(raw_data, alignof(Entry));
  const Entry* entries = reinterpret_cast<const Entry*>(raw_data);
  size_t mask_bits = CalculateMaskBits(num_class_defs);
  return TypeLookupTable(dex_data_pointer, mask_bits, entries, /* owned_entries= */ nullptr);
}
 
uint32_t TypeLookupTable::Lookup(const char* str, uint32_t hash) const {
  uint32_t mask = Entry::GetMask(mask_bits_);
  uint32_t pos = hash & mask;
  // Thanks to special insertion algorithm, the element at position pos can be empty
  // or start of the right bucket, or anywhere in the wrong bucket's chain.
  const Entry* entry = &entries_[pos];
  if (entry->IsEmpty()) {
    return dex::kDexNoIndex;
  }
  // Look for the partial hash match first, even if traversing the wrong bucket's chain.
  uint32_t compared_hash_bits = (hash << mask_bits_) >> (2 * mask_bits_);
  while (compared_hash_bits != entry->GetHashBits(mask_bits_)) {
    if (entry->IsLast(mask_bits_)) {
      return dex::kDexNoIndex;
    }
    pos = (pos + entry->GetNextPosDelta(mask_bits_)) & mask;
    entry = &entries_[pos];
    DCHECK(!entry->IsEmpty());
  }
  // Found partial hash match, compare strings (expecting this to succeed).
  const char* first_checked_str = GetStringData(*entry);
  if (ModifiedUtf8StringEquals(str, first_checked_str)) {
    return entry->GetClassDefIdx(mask_bits_);
  }
  // If we're at the end of the chain, return before doing further expensive work.
  if (entry->IsLast(mask_bits_)) {
    return dex::kDexNoIndex;
  }
  // Check if we're traversing the right bucket. This is important if the compared
  // partial hash has only a few bits (i.e. it can match frequently).
  if (((ComputeModifiedUtf8Hash(first_checked_str) ^ hash) & mask) != 0u) {
    return dex::kDexNoIndex;  // Low hash bits mismatch.
  }
  // Continue looking for the string in the rest of the chain.
  do {
    pos = (pos + entry->GetNextPosDelta(mask_bits_)) & mask;
    entry = &entries_[pos];
    DCHECK(!entry->IsEmpty());
    if (compared_hash_bits == entry->GetHashBits(mask_bits_) &&
        ModifiedUtf8StringEquals(str, GetStringData(*entry))) {
      return entry->GetClassDefIdx(mask_bits_);
    }
  } while (!entry->IsLast(mask_bits_));
  // Not found.
  return dex::kDexNoIndex;
}
 
uint32_t TypeLookupTable::RawDataLength(uint32_t num_class_defs) {
  return SupportedSize(num_class_defs) ? RoundUpToPowerOfTwo(num_class_defs) * sizeof(Entry) : 0u;
}
 
uint32_t TypeLookupTable::CalculateMaskBits(uint32_t num_class_defs) {
  return SupportedSize(num_class_defs) ? MinimumBitsToStore(num_class_defs - 1u) : 0u;
}
 
bool TypeLookupTable::SupportedSize(uint32_t num_class_defs) {
  return num_class_defs != 0u && num_class_defs <= std::numeric_limits<uint16_t>::max();
}
 
TypeLookupTable::TypeLookupTable(const uint8_t* dex_data_pointer,
                                 uint32_t mask_bits,
                                 const Entry* entries,
                                 std::unique_ptr<Entry[]> owned_entries)
    : dex_data_begin_(dex_data_pointer),
      mask_bits_(mask_bits),
      entries_(entries),
      owned_entries_(std::move(owned_entries)) {}
 
const char* TypeLookupTable::GetStringData(const Entry& entry) const {
  DCHECK(dex_data_begin_ != nullptr);
  const uint8_t* ptr = dex_data_begin_ + entry.GetStringOffset();
  // Skip string length.
  DecodeUnsignedLeb128(&ptr);
  return reinterpret_cast<const char*>(ptr);
}
 
}  // namespace art