1 /*
  2  * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  *
 23  */
 24 
 25 #include "memory/allocation.hpp"
 26 #include "memory/universe.hpp"
 27 #include "oops/fieldStreams.inline.hpp"
 28 #include "oops/oop.inline.hpp"
 29 #include "oops/weakHandle.inline.hpp"
 30 #include "prims/jvmtiExport.hpp"
 31 #include "prims/jvmtiTagMapTable.hpp"
 32 
 33 
 34 JvmtiTagMapKey::JvmtiTagMapKey(oop obj) : _obj(obj) {}
 35 
 36 JvmtiTagMapKey::JvmtiTagMapKey(const JvmtiTagMapKey& src) : _h() {
 37   // move object into Handle when copying into the table
 38   if (src._obj != nullptr) {
 39     _is_weak = !src._obj->klass()->is_inline_klass();
 40 
 41     // obj was read with AS_NO_KEEPALIVE, or equivalent, like during
 42     // a heap walk.  The object needs to be kept alive when it is published.
 43     Universe::heap()->keep_alive(src._obj);
 44 
 45     if (_is_weak) {
 46       _wh = WeakHandle(JvmtiExport::weak_tag_storage(), src._obj);
 47     } else {
 48       _h = OopHandle(JvmtiExport::jvmti_oop_storage(), src._obj);
 49     }
 50   } else {
 51     // resizing needs to create a copy.
 52     if (_is_weak) {
 53       _wh = src._wh;
 54     } else {
 55       _h = src._h;
 56     }
 57   }
 58   // obj is always null after a copy.
 59   _obj = nullptr;
 60 }
 61 
 62 void JvmtiTagMapKey::release_handle() {
 63   if (_is_weak) {
 64     _wh.release(JvmtiExport::weak_tag_storage());
 65   } else {
 66     _h.release(JvmtiExport::jvmti_oop_storage());
 67   }
 68 }
 69 
 70 oop JvmtiTagMapKey::object() const {
 71   assert(_obj == nullptr, "Must have a handle and not object");
 72   return _is_weak ? _wh.resolve() : _h.resolve();
 73 }
 74 
 75 oop JvmtiTagMapKey::object_no_keepalive() const {
 76   assert(_obj == nullptr, "Must have a handle and not object");
 77   return _is_weak ? _wh.peek() : _h.peek();
 78 }
 79 
 80 unsigned JvmtiTagMapKey::get_hash(const JvmtiTagMapKey& entry) {
 81   oop obj = entry._obj;
 82   assert(obj != nullptr, "must lookup obj to hash");
 83   if (obj->is_inline_type()) {
 84     // For inline types, use the klass as a hash code and let the equals match the obj.
 85     // It might have a long bucket but TBD to improve this if a customer situation arises.
 86     return (unsigned)((int64_t)obj->klass() >> 3);
 87   } else {
 88     return (unsigned)obj->identity_hash();
 89   }
 90 }
 91 
 92 static bool equal_oops(oop obj1, oop obj2); // forward declaration
 93 
 94 static bool equal_fields(char type, oop obj1, oop obj2, int offset) {
 95   switch (type) {
 96   case JVM_SIGNATURE_BOOLEAN:
 97     return obj1->bool_field(offset) == obj2->bool_field(offset);
 98   case JVM_SIGNATURE_CHAR:
 99     return obj1->char_field(offset) == obj2->char_field(offset);
100   case JVM_SIGNATURE_FLOAT:
101     return obj1->float_field(offset) == obj2->float_field(offset);
102   case JVM_SIGNATURE_DOUBLE:
103     return obj1->double_field(offset) == obj2->double_field(offset);
104   case JVM_SIGNATURE_BYTE:
105     return obj1->byte_field(offset) == obj2->byte_field(offset);
106   case JVM_SIGNATURE_SHORT:
107     return obj1->short_field(offset) == obj2->short_field(offset);
108   case JVM_SIGNATURE_INT:
109     return obj1->int_field(offset) == obj2->int_field(offset);
110   case JVM_SIGNATURE_LONG:
111     return obj1->long_field(offset) == obj2->long_field(offset);
112   case JVM_SIGNATURE_CLASS:
113   case JVM_SIGNATURE_ARRAY:
114     return equal_oops(obj1->obj_field(offset), obj2->obj_field(offset));
115   }
116   ShouldNotReachHere();
117 }
118 
119 // For heap-allocated objects offset is 0 and 'klass' is obj1->klass() (== obj2->klass()).
120 // For flattened objects offset is the offset in the holder object, 'klass' is inlined object class.
121 static bool equal_value_objects(oop obj1, oop obj2, InlineKlass* klass, int offset) {
122   for (JavaFieldStream fld(klass); !fld.done(); fld.next()) {
123     // ignore static fields
124     if (fld.access_flags().is_static()) {
125       continue;
126     }
127     int field_offset = offset + fld.offset() - (offset > 0 ? klass->payload_offset() : 0);
128     if (fld.is_flat()) { // flat value field
129       InstanceKlass* holder_klass = fld.field_holder();
130       InlineKlass* field_klass = holder_klass->get_inline_type_field_klass(fld.index());
131       if (!equal_value_objects(obj1, obj2, field_klass, field_offset)) {
132         return false;
133       }
134     } else {
135       if (!equal_fields(fld.signature()->char_at(0), obj1, obj2, field_offset)) {
136         return false;
137       }
138     }
139   }
140   return true;
141 }
142 
143 static bool equal_oops(oop obj1, oop obj2) {
144   if (obj1 == obj2) {
145     return true;
146   }
147 
148   if (EnableValhalla) {
149     if (obj1 != nullptr && obj2 != nullptr && obj1->klass() == obj2->klass() && obj1->is_inline_type()) {
150       InlineKlass* vk = InlineKlass::cast(obj1->klass());
151       return equal_value_objects(obj1, obj2, vk, 0);
152     }
153   }
154   return false;
155 }
156 
157 bool JvmtiTagMapKey::equals(const JvmtiTagMapKey& lhs, const JvmtiTagMapKey& rhs) {
158   oop lhs_obj = lhs._obj != nullptr ? lhs._obj : lhs.object_no_keepalive();
159   oop rhs_obj = rhs._obj != nullptr ? rhs._obj : rhs.object_no_keepalive();
160   return equal_oops(lhs_obj, rhs_obj);
161 }
162 
163 // Inline types don't use hash for this table.
164 static inline bool fast_no_hash_check(oop obj) {
165   return (obj->fast_no_hash_check() && !obj->is_inline_type());
166 }
167 
168 static const int INITIAL_TABLE_SIZE = 1007;
169 static const int MAX_TABLE_SIZE     = 0x3fffffff;
170 
171 JvmtiTagMapTable::JvmtiTagMapTable() : _table(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE) {}
172 
173 void JvmtiTagMapTable::clear() {
174   struct RemoveAll {
175     bool do_entry(JvmtiTagMapKey& entry, const jlong& tag) {
176       entry.release_handle();
177       return true;
178     }
179   } remove_all;
180   // The unlink method of ResourceHashTable gets a pointer to a type whose 'do_entry(K,V)' method is callled
181   // while iterating over all the elements of the table. If the do_entry() method returns true the element
182   // will be removed.
183   // In this case, we always return true from do_entry to clear all the elements.
184   _table.unlink(&remove_all);
185 
186   assert(_table.number_of_entries() == 0, "should have removed all entries");
187 }
188 
189 JvmtiTagMapTable::~JvmtiTagMapTable() {
190   clear();
191 }
192 
193 jlong JvmtiTagMapTable::find(oop obj) {
194   if (is_empty()) {
195     return 0;
196   }
197 
198   if (fast_no_hash_check(obj)) {
199     // Objects in the table all have a hashcode, unless inlined types.
200     return 0;
201   }
202 
203   JvmtiTagMapKey jtme(obj);
204   jlong* found = _table.get(jtme);
205   return found == nullptr ? 0 : *found;
206 }
207 
208 void JvmtiTagMapTable::add(oop obj, jlong tag) {
209   JvmtiTagMapKey new_entry(obj);
210   bool is_added;
211   if (fast_no_hash_check(obj)) {
212     // Can't be in the table so add it fast.
213     is_added = _table.put_when_absent(new_entry, tag);
214   } else {
215     jlong* value = _table.put_if_absent(new_entry, tag, &is_added);
216     *value = tag; // assign the new tag
217   }
218   if (is_added) {
219     if (_table.maybe_grow(5, true /* use_large_table_sizes */)) {
220       int max_bucket_size = DEBUG_ONLY(_table.verify()) NOT_DEBUG(0);
221       log_info(jvmti, table) ("JvmtiTagMap table resized to %d for %d entries max bucket %d",
222                               _table.table_size(), _table.number_of_entries(), max_bucket_size);
223     }
224   }
225 }
226 
227 void JvmtiTagMapTable::remove(oop obj) {
228   JvmtiTagMapKey jtme(obj);
229   auto clean = [] (JvmtiTagMapKey& entry, jlong tag) {
230     entry.release_handle();
231   };
232   _table.remove(jtme, clean);
233 }
234 
235 void JvmtiTagMapTable::entry_iterate(JvmtiTagMapKeyClosure* closure) {
236   _table.iterate(closure);
237 }
238 
239 void JvmtiTagMapTable::remove_dead_entries(GrowableArray<jlong>* objects) {
240   struct IsDead {
241     GrowableArray<jlong>* _objects;
242     IsDead(GrowableArray<jlong>* objects) : _objects(objects) {}
243     bool do_entry(JvmtiTagMapKey& entry, jlong tag) {
244       if (entry.object_no_keepalive() == nullptr) {
245         if (_objects != nullptr) {
246           _objects->append(tag);
247         }
248         entry.release_handle();
249         return true;
250       }
251       return false;;
252     }
253   } is_dead(objects);
254   _table.unlink(&is_dead);
255 }