indirect_reference_table.cc revision dc51b79e65abcdad094ccd5e5a2caf5153433d49
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.h"
18#include "jni_internal.h"
19#include "reference_table.h"
20#include "runtime.h"
21#include "utils.h"
22
23#include <cstdlib>
24
25namespace art {
26
27static void AbortMaybe() {
28  // If -Xcheck:jni is on, it'll give a more detailed error before aborting.
29  if (!Runtime::Current()->GetJavaVM()->check_jni) {
30    // Otherwise, we want to abort rather than hand back a bad reference.
31    LOG(FATAL) << "JNI ERROR (app bug): see above.";
32  }
33}
34
35IndirectReferenceTable::IndirectReferenceTable(size_t initialCount,
36    size_t maxCount, IndirectRefKind desiredKind)
37{
38  CHECK_GT(initialCount, 0U);
39  CHECK_LE(initialCount, maxCount);
40  CHECK_NE(desiredKind, kSirtOrInvalid);
41
42  table_ = reinterpret_cast<const Object**>(malloc(initialCount * sizeof(const Object*)));
43  CHECK(table_ != NULL);
44#ifndef NDEBUG
45  memset(table_, 0xd1, initialCount * sizeof(const Object*));
46#endif
47
48  slot_data_ = reinterpret_cast<IndirectRefSlot*>(calloc(initialCount, sizeof(IndirectRefSlot)));
49  CHECK(slot_data_ != NULL);
50
51  segment_state_.all = IRT_FIRST_SEGMENT;
52  alloc_entries_ = initialCount;
53  max_entries_ = maxCount;
54  kind_ = desiredKind;
55}
56
57IndirectReferenceTable::~IndirectReferenceTable() {
58  free(table_);
59  free(slot_data_);
60  table_ = NULL;
61  slot_data_ = NULL;
62  alloc_entries_ = max_entries_ = -1;
63}
64
65/*
66 * Make sure that the entry at "idx" is correctly paired with "iref".
67 */
68bool IndirectReferenceTable::CheckEntry(const char* what, IndirectRef iref, int idx) const {
69  const Object* obj = table_[idx];
70  IndirectRef checkRef = ToIndirectRef(obj, idx);
71  if (checkRef != iref) {
72    LOG(ERROR) << "JNI ERROR (app bug): attempt to " << what
73               << " stale " << kind_ << " " << iref
74               << " (should be " << checkRef << ")";
75    AbortMaybe();
76    return false;
77  }
78  return true;
79}
80
81IndirectRef IndirectReferenceTable::Add(uint32_t cookie, const Object* obj) {
82  IRTSegmentState prevState;
83  prevState.all = cookie;
84  size_t topIndex = segment_state_.parts.topIndex;
85
86  DCHECK(obj != NULL);
87  // TODO: stronger sanity check on the object (such as in heap)
88  DCHECK(IsAligned(reinterpret_cast<intptr_t>(obj), 8));
89  DCHECK(table_ != NULL);
90  DCHECK_LE(alloc_entries_, max_entries_);
91  DCHECK_GE(segment_state_.parts.numHoles, prevState.parts.numHoles);
92
93  if (topIndex == alloc_entries_) {
94    /* reached end of allocated space; did we hit buffer max? */
95    if (topIndex == max_entries_) {
96      LOG(ERROR) << "JNI ERROR (app bug): " << kind_ << " table overflow "
97                 << "(max=" << max_entries_ << ")";
98      Dump();
99      LOG(FATAL); // TODO: operator<< for IndirectReferenceTable
100    }
101
102    size_t newSize = alloc_entries_ * 2;
103    if (newSize > max_entries_) {
104      newSize = max_entries_;
105    }
106    DCHECK_GT(newSize, alloc_entries_);
107
108    table_ = (const Object**) realloc(table_, newSize * sizeof(const Object*));
109    slot_data_ = (IndirectRefSlot*) realloc(slot_data_, newSize * sizeof(IndirectRefSlot));
110    if (table_ == NULL || slot_data_ == NULL) {
111      LOG(ERROR) << "JNI ERROR (app bug): unable to expand "
112                 << kind_ << " table (from "
113                 << alloc_entries_ << " to " << newSize
114                 << ", max=" << max_entries_ << ")";
115      Dump();
116      LOG(FATAL); // TODO: operator<< for IndirectReferenceTable
117    }
118
119    // Clear the newly-allocated slot_data_ elements.
120    memset(slot_data_ + alloc_entries_, 0, (newSize - alloc_entries_) * sizeof(IndirectRefSlot));
121
122    alloc_entries_ = newSize;
123  }
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   */
130  IndirectRef result;
131  int numHoles = segment_state_.parts.numHoles - prevState.parts.numHoles;
132  if (numHoles > 0) {
133    DCHECK_GT(topIndex, 1U);
134    /* find the first hole; likely to be near the end of the list */
135    const Object** pScan = &table_[topIndex - 1];
136    DCHECK(*pScan != NULL);
137    while (*--pScan != NULL) {
138      DCHECK_GE(pScan, table_ + prevState.parts.topIndex);
139    }
140    UpdateSlotAdd(obj, pScan - table_);
141    result = ToIndirectRef(obj, pScan - table_);
142    *pScan = obj;
143    segment_state_.parts.numHoles--;
144  } else {
145    /* add to the end */
146    UpdateSlotAdd(obj, topIndex);
147    result = ToIndirectRef(obj, topIndex);
148    table_[topIndex++] = obj;
149    segment_state_.parts.topIndex = topIndex;
150  }
151
152  DCHECK(result != NULL);
153  return result;
154}
155
156/*
157 * Verify that the indirect table lookup is valid.
158 *
159 * Returns "false" if something looks bad.
160 */
161bool IndirectReferenceTable::GetChecked(IndirectRef iref) const {
162  if (iref == NULL) {
163    LOG(WARNING) << "Attempt to look up NULL " << kind_;
164    return false;
165  }
166  if (GetIndirectRefKind(iref) == kSirtOrInvalid) {
167    LOG(ERROR) << "JNI ERROR (app bug): invalid " << kind_ << " " << iref;
168    AbortMaybe();
169    return false;
170  }
171
172  int topIndex = segment_state_.parts.topIndex;
173  int idx = ExtractIndex(iref);
174  if (idx >= topIndex) {
175    /* bad -- stale reference? */
176    LOG(ERROR) << "JNI ERROR (app bug): accessed stale " << kind_ << " " << iref << " (index " << idx << " in a table of size " << topIndex << ")";
177    AbortMaybe();
178    return false;
179  }
180
181  if (table_[idx] == NULL) {
182    LOG(ERROR) << "JNI ERROR (app bug): accessed deleted " << kind_ << " " << iref;
183    AbortMaybe();
184    return false;
185  }
186
187  if (!CheckEntry("use", iref, idx)) {
188    return false;
189  }
190
191  return true;
192}
193
194static int LinearScan(IndirectRef iref, int bottomIndex, int topIndex, const Object** table) {
195  for (int i = bottomIndex; i < topIndex; ++i) {
196    if (table[i] == reinterpret_cast<const Object*>(iref)) {
197      return i;
198    }
199  }
200  return -1;
201}
202
203bool IndirectReferenceTable::Contains(IndirectRef iref) const {
204  return LinearScan(iref, 0, segment_state_.parts.topIndex, table_) != -1;
205}
206
207/*
208 * Remove "obj" from "pRef".  We extract the table offset bits from "iref"
209 * and zap the corresponding entry, leaving a hole if it's not at the top.
210 *
211 * If the entry is not between the current top index and the bottom index
212 * specified by the cookie, we don't remove anything.  This is the behavior
213 * required by JNI's DeleteLocalRef function.
214 *
215 * Note this is NOT called when a local frame is popped.  This is only used
216 * for explicit single removals.
217 *
218 * Returns "false" if nothing was removed.
219 */
220bool IndirectReferenceTable::Remove(uint32_t cookie, IndirectRef iref) {
221  IRTSegmentState prevState;
222  prevState.all = cookie;
223  int topIndex = segment_state_.parts.topIndex;
224  int bottomIndex = prevState.parts.topIndex;
225
226  DCHECK(table_ != NULL);
227  DCHECK_LE(alloc_entries_, max_entries_);
228  DCHECK_GE(segment_state_.parts.numHoles, prevState.parts.numHoles);
229
230  int idx = ExtractIndex(iref);
231
232  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
233  if (GetIndirectRefKind(iref) == kSirtOrInvalid || vm->work_around_app_jni_bugs) {
234    idx = LinearScan(iref, bottomIndex, topIndex, table_);
235    if (idx == -1) {
236      LOG(WARNING) << "trying to work around app JNI bugs, but didn't find " << iref << " in table!";
237      return false;
238    }
239  }
240
241  if (idx < bottomIndex) {
242    /* wrong segment */
243    LOG(INFO) << "Attempt to remove index outside index area (" << idx << " vs " << bottomIndex << "-" << topIndex << ")";
244    return false;
245  }
246  if (idx >= topIndex) {
247    /* bad -- stale reference? */
248    LOG(INFO) << "Attempt to remove invalid index " << idx << " (bottom=" << bottomIndex << " top=" << topIndex << ")";
249    return false;
250  }
251
252  if (idx == topIndex-1) {
253    // Top-most entry.  Scan up and consume holes.
254
255    if (!vm->work_around_app_jni_bugs && !CheckEntry("remove", iref, idx)) {
256      return false;
257    }
258
259    table_[idx] = NULL;
260    int numHoles = segment_state_.parts.numHoles - prevState.parts.numHoles;
261    if (numHoles != 0) {
262      while (--topIndex > bottomIndex && numHoles != 0) {
263        //LOG(INFO) << "+++ checking for hole at " << topIndex-1 << " (cookie=" << cookie << ") val=" << table_[topIndex-1];
264        if (table_[topIndex-1] != NULL) {
265          break;
266        }
267        //LOG(INFO) << "+++ ate hole at " << (topIndex-1);
268        numHoles--;
269      }
270      segment_state_.parts.numHoles = numHoles + prevState.parts.numHoles;
271      segment_state_.parts.topIndex = topIndex;
272    } else {
273      segment_state_.parts.topIndex = topIndex-1;
274      //LOG(INFO) << "+++ ate last entry " << topIndex-1;
275    }
276  } else {
277    /*
278     * Not the top-most entry.  This creates a hole.  We NULL out the
279     * entry to prevent somebody from deleting it twice and screwing up
280     * the hole count.
281     */
282    if (table_[idx] == NULL) {
283      LOG(INFO) << "--- WEIRD: removing null entry " << idx;
284      return false;
285    }
286    if (!vm->work_around_app_jni_bugs && !CheckEntry("remove", iref, idx)) {
287      return false;
288    }
289
290    table_[idx] = NULL;
291    segment_state_.parts.numHoles++;
292    //LOG(INFO) << "+++ left hole at " << idx << ", holes=" << segment_state_.parts.numHoles;
293  }
294
295  return true;
296}
297
298void IndirectReferenceTable::VisitRoots(Heap::RootVisitor* visitor, void* arg) {
299  typedef IndirectReferenceTable::iterator It; // TODO: C++0x auto
300  for (It it = begin(), end = this->end(); it != end; ++it) {
301    visitor(**it, arg);
302  }
303}
304
305std::ostream& operator<<(std::ostream& os, IndirectRefKind rhs) {
306  switch (rhs) {
307  case kSirtOrInvalid:
308    os << "stack indirect reference table or invalid reference";
309    break;
310  case kLocal:
311    os << "local reference";
312    break;
313  case kGlobal:
314    os << "global reference";
315    break;
316  case kWeakGlobal:
317    os << "weak global reference";
318    break;
319  default:
320    os << "IndirectRefKind[" << static_cast<int>(rhs) << "]";
321    break;
322  }
323  return os;
324}
325
326void IndirectReferenceTable::Dump() const {
327  LOG(WARNING) << kind_ << " table dump:";
328  std::vector<const Object*> entries(table_, table_ + Capacity());
329  ReferenceTable::Dump(entries);
330}
331
332}  // namespace art
333