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