indirect_reference_table.cc revision c56057e40938c587a74984651a510e320a8cb4fd
1/*
2 * Copyright (C) 2009 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 "indirect_reference_table-inl.h"
18
19#include "jni_internal.h"
20#include "reference_table.h"
21#include "runtime.h"
22#include "scoped_thread_state_change.h"
23#include "thread.h"
24#include "utils.h"
25#include "verify_object-inl.h"
26
27#include <cstdlib>
28
29namespace art {
30
31template<typename T>
32class MutatorLockedDumpable {
33 public:
34  explicit MutatorLockedDumpable(T& value)
35      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) : value_(value) {
36  }
37
38  void Dump(std::ostream& os) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
39    value_.Dump(os);
40  }
41
42 private:
43  T& value_;
44
45  DISALLOW_COPY_AND_ASSIGN(MutatorLockedDumpable);
46};
47
48template<typename T>
49std::ostream& operator<<(std::ostream& os, const MutatorLockedDumpable<T>& rhs)
50// TODO: should be SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) however annotalysis
51//       currently fails for this.
52    NO_THREAD_SAFETY_ANALYSIS {
53  rhs.Dump(os);
54  return os;
55}
56
57void IndirectReferenceTable::AbortIfNoCheckJNI() {
58  // If -Xcheck:jni is on, it'll give a more detailed error before aborting.
59  if (!Runtime::Current()->GetJavaVM()->check_jni) {
60    // Otherwise, we want to abort rather than hand back a bad reference.
61    LOG(FATAL) << "JNI ERROR (app bug): see above.";
62  }
63}
64
65IndirectReferenceTable::IndirectReferenceTable(size_t initialCount,
66                                               size_t maxCount, IndirectRefKind desiredKind) {
67  CHECK_GT(initialCount, 0U);
68  CHECK_LE(initialCount, maxCount);
69  CHECK_NE(desiredKind, kSirtOrInvalid);
70
71  std::string error_str;
72  const size_t initial_bytes = initialCount * sizeof(const mirror::Object*);
73  const size_t table_bytes = maxCount * sizeof(const mirror::Object*);
74  table_mem_map_.reset(MemMap::MapAnonymous("indirect ref table", nullptr, table_bytes,
75                                            PROT_READ | PROT_WRITE, false, &error_str));
76  CHECK(table_mem_map_.get() != nullptr) << error_str;
77
78  table_ = reinterpret_cast<mirror::Object**>(table_mem_map_->Begin());
79  CHECK(table_ != nullptr);
80  memset(table_, 0xd1, initial_bytes);
81
82  const size_t slot_bytes = maxCount * sizeof(IndirectRefSlot);
83  slot_mem_map_.reset(MemMap::MapAnonymous("indirect ref table slots", nullptr, slot_bytes,
84                                           PROT_READ | PROT_WRITE, false, &error_str));
85  CHECK(slot_mem_map_.get() != nullptr) << error_str;
86  slot_data_ = reinterpret_cast<IndirectRefSlot*>(slot_mem_map_->Begin());
87  CHECK(slot_data_ != nullptr);
88
89  segment_state_.all = IRT_FIRST_SEGMENT;
90  alloc_entries_ = initialCount;
91  max_entries_ = maxCount;
92  kind_ = desiredKind;
93}
94
95IndirectReferenceTable::~IndirectReferenceTable() {
96}
97
98IndirectRef IndirectReferenceTable::Add(uint32_t cookie, mirror::Object* obj) {
99  IRTSegmentState prevState;
100  prevState.all = cookie;
101  size_t topIndex = segment_state_.parts.topIndex;
102
103  CHECK(obj != NULL);
104  VerifyObject(obj);
105  DCHECK(table_ != NULL);
106  DCHECK_LE(alloc_entries_, max_entries_);
107  DCHECK_GE(segment_state_.parts.numHoles, prevState.parts.numHoles);
108
109  if (topIndex == alloc_entries_) {
110    // reached end of allocated space; did we hit buffer max?
111    if (topIndex == max_entries_) {
112      LOG(FATAL) << "JNI ERROR (app bug): " << kind_ << " table overflow "
113                 << "(max=" << max_entries_ << ")\n"
114                 << MutatorLockedDumpable<IndirectReferenceTable>(*this);
115    }
116
117    size_t newSize = alloc_entries_ * 2;
118    if (newSize > max_entries_) {
119      newSize = max_entries_;
120    }
121    DCHECK_GT(newSize, alloc_entries_);
122
123    alloc_entries_ = newSize;
124  }
125
126  // We know there's enough room in the table.  Now we just need to find
127  // the right spot.  If there's a hole, find it and fill it; otherwise,
128  // add to the end of the list.
129  IndirectRef result;
130  int numHoles = segment_state_.parts.numHoles - prevState.parts.numHoles;
131  if (numHoles > 0) {
132    DCHECK_GT(topIndex, 1U);
133    // Find the first hole; likely to be near the end of the list.
134    mirror::Object** pScan = &table_[topIndex - 1];
135    DCHECK(*pScan != NULL);
136    while (*--pScan != NULL) {
137      DCHECK_GE(pScan, table_ + prevState.parts.topIndex);
138    }
139    UpdateSlotAdd(obj, pScan - table_);
140    result = ToIndirectRef(obj, pScan - table_);
141    *pScan = obj;
142    segment_state_.parts.numHoles--;
143  } else {
144    // Add to the end.
145    UpdateSlotAdd(obj, topIndex);
146    result = ToIndirectRef(obj, topIndex);
147    table_[topIndex++] = obj;
148    segment_state_.parts.topIndex = topIndex;
149  }
150  if (false) {
151    LOG(INFO) << "+++ added at " << ExtractIndex(result) << " top=" << segment_state_.parts.topIndex
152              << " holes=" << segment_state_.parts.numHoles;
153  }
154
155  DCHECK(result != NULL);
156  return result;
157}
158
159void IndirectReferenceTable::AssertEmpty() {
160  if (UNLIKELY(begin() != end())) {
161    ScopedObjectAccess soa(Thread::Current());
162    LOG(FATAL) << "Internal Error: non-empty local reference table\n"
163               << MutatorLockedDumpable<IndirectReferenceTable>(*this);
164  }
165}
166
167// Removes an object. We extract the table offset bits from "iref"
168// and zap the corresponding entry, leaving a hole if it's not at the top.
169// If the entry is not between the current top index and the bottom index
170// specified by the cookie, we don't remove anything. This is the behavior
171// required by JNI's DeleteLocalRef function.
172// This method is not called when a local frame is popped; this is only used
173// for explicit single removals.
174// Returns "false" if nothing was removed.
175bool IndirectReferenceTable::Remove(uint32_t cookie, IndirectRef iref) {
176  IRTSegmentState prevState;
177  prevState.all = cookie;
178  int topIndex = segment_state_.parts.topIndex;
179  int bottomIndex = prevState.parts.topIndex;
180
181  DCHECK(table_ != NULL);
182  DCHECK_LE(alloc_entries_, max_entries_);
183  DCHECK_GE(segment_state_.parts.numHoles, prevState.parts.numHoles);
184
185  int idx = ExtractIndex(iref);
186
187  if (GetIndirectRefKind(iref) == kSirtOrInvalid &&
188      Thread::Current()->SirtContains(reinterpret_cast<jobject>(iref))) {
189    LOG(WARNING) << "Attempt to remove local SIRT entry from IRT, ignoring";
190    return true;
191  }
192
193  if (idx < bottomIndex) {
194    // Wrong segment.
195    LOG(WARNING) << "Attempt to remove index outside index area (" << idx
196                 << " vs " << bottomIndex << "-" << topIndex << ")";
197    return false;
198  }
199  if (idx >= topIndex) {
200    // Bad --- stale reference?
201    LOG(WARNING) << "Attempt to remove invalid index " << idx
202                 << " (bottom=" << bottomIndex << " top=" << topIndex << ")";
203    return false;
204  }
205
206  if (idx == topIndex-1) {
207    // Top-most entry.  Scan up and consume holes.
208
209    if (!CheckEntry("remove", iref, idx)) {
210      return false;
211    }
212
213    table_[idx] = NULL;
214    int numHoles = segment_state_.parts.numHoles - prevState.parts.numHoles;
215    if (numHoles != 0) {
216      while (--topIndex > bottomIndex && numHoles != 0) {
217        if (false) {
218          LOG(INFO) << "+++ checking for hole at " << topIndex-1
219                    << " (cookie=" << cookie << ") val=" << table_[topIndex - 1];
220        }
221        if (table_[topIndex-1] != NULL) {
222          break;
223        }
224        if (false) {
225          LOG(INFO) << "+++ ate hole at " << (topIndex - 1);
226        }
227        numHoles--;
228      }
229      segment_state_.parts.numHoles = numHoles + prevState.parts.numHoles;
230      segment_state_.parts.topIndex = topIndex;
231    } else {
232      segment_state_.parts.topIndex = topIndex-1;
233      if (false) {
234        LOG(INFO) << "+++ ate last entry " << topIndex - 1;
235      }
236    }
237  } else {
238    // Not the top-most entry.  This creates a hole.  We NULL out the
239    // entry to prevent somebody from deleting it twice and screwing up
240    // the hole count.
241    if (table_[idx] == NULL) {
242      LOG(INFO) << "--- WEIRD: removing null entry " << idx;
243      return false;
244    }
245    if (!CheckEntry("remove", iref, idx)) {
246      return false;
247    }
248
249    table_[idx] = NULL;
250    segment_state_.parts.numHoles++;
251    if (false) {
252      LOG(INFO) << "+++ left hole at " << idx << ", holes=" << segment_state_.parts.numHoles;
253    }
254  }
255
256  return true;
257}
258
259void IndirectReferenceTable::VisitRoots(RootCallback* callback, void* arg, uint32_t tid,
260                                        RootType root_type) {
261  for (auto ref : *this) {
262    callback(ref, arg, tid, root_type);
263    DCHECK(*ref != nullptr);
264  }
265}
266
267void IndirectReferenceTable::Dump(std::ostream& os) const {
268  os << kind_ << " table dump:\n";
269  ReferenceTable::Table entries(table_, table_ + Capacity());
270  // Remove NULLs.
271  for (int i = entries.size() - 1; i >= 0; --i) {
272    if (entries[i] == NULL) {
273      entries.erase(entries.begin() + i);
274    }
275  }
276  ReferenceTable::Dump(os, entries);
277}
278
279}  // namespace art
280