| /* | |
| * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| * | |
| */ | |
| #include "memory/resourceArea.hpp" | |
| #include "cds/cdsConfig.hpp" | |
| #include "oops/fieldInfo.inline.hpp" | |
| #include "runtime/atomic.hpp" | |
| #include "utilities/packedTable.hpp" | |
| void FieldInfo::print(outputStream* os, ConstantPool* cp) { | |
| os->print_cr("index=%d name_index=%d name=%s signature_index=%d signature=%s offset=%d " | |
| "AccessFlags=%d FieldFlags=%d " | |
| "initval_index=%d gen_signature_index=%d, gen_signature=%s contended_group=%d", | |
| index(), | |
| name_index(), name(cp)->as_utf8(), | |
| signature_index(), signature(cp)->as_utf8(), | |
| offset(), | |
| access_flags().as_field_flags(), | |
| field_flags().as_uint(), | |
| initializer_index(), | |
| generic_signature_index(), | |
| _field_flags.is_generic() ? (_field_flags.is_injected() ? | |
| lookup_symbol(generic_signature_index())->as_utf8() : cp->symbol_at(generic_signature_index())->as_utf8() | |
| ) : "", | |
| is_contended() ? contended_group() : 0); | |
| } | |
| void FieldInfo::print_from_growable_array(outputStream* os, GrowableArray<FieldInfo>* array, ConstantPool* cp) { | |
| for (int i = 0; i < array->length(); i++) { | |
| array->adr_at(i)->print(os, cp); | |
| } | |
| } | |
| Array<u1>* FieldInfoStream::create_FieldInfoStream(GrowableArray<FieldInfo>* fields, int java_fields, int injected_fields, | |
| ClassLoaderData* loader_data, TRAPS) { | |
| // The stream format described in fieldInfo.hpp is: | |
| // FieldInfoStream := j=num_java_fields k=num_injected_fields Field[j+k] End | |
| // Field := name sig offset access flags Optionals(flags) | |
| // Optionals(i) := initval?[i&is_init] // ConstantValue attr | |
| // gsig?[i&is_generic] // signature attr | |
| // group?[i&is_contended] // Contended anno (group) | |
| // End = 0 | |
| using StreamSizer = UNSIGNED5::Sizer<>; | |
| using StreamFieldSizer = Mapper<StreamSizer>; | |
| StreamSizer s; | |
| StreamFieldSizer sizer(&s); | |
| assert(fields->length() == java_fields + injected_fields, "must be"); | |
| sizer.consumer()->accept_uint(java_fields); | |
| sizer.consumer()->accept_uint(injected_fields); | |
| for (int i = 0; i < fields->length(); i++) { | |
| FieldInfo* fi = fields->adr_at(i); | |
| sizer.map_field_info(*fi); | |
| } | |
| // Originally there was an extra byte with 0 terminating the reading; | |
| // now we check limits instead. | |
| int storage_size = sizer.consumer()->position(); | |
| Array<u1>* const fis = MetadataFactory::new_array<u1>(loader_data, storage_size, CHECK_NULL); | |
| using StreamWriter = UNSIGNED5::Writer<Array<u1>*, int, ArrayHelper<Array<u1>*, int>>; | |
| using StreamFieldWriter = Mapper<StreamWriter>; | |
| StreamWriter w(fis); | |
| StreamFieldWriter writer(&w); | |
| writer.consumer()->accept_uint(java_fields); | |
| writer.consumer()->accept_uint(injected_fields); | |
| for (int i = 0; i < fields->length(); i++) { | |
| writer.map_field_info(fields->at(i)); | |
| } | |
| #ifdef ASSERT | |
| FieldInfoReader r(fis); | |
| int jfc, ifc; | |
| r.read_field_counts(&jfc, &ifc); | |
| assert(jfc == java_fields, "Must be"); | |
| assert(ifc == injected_fields, "Must be"); | |
| for (int i = 0; i < jfc + ifc; i++) { | |
| FieldInfo fi; | |
| r.read_field_info(fi); | |
| FieldInfo* fi_ref = fields->adr_at(i); | |
| assert(fi_ref->name_index() == fi.name_index(), "Must be"); | |
| assert(fi_ref->signature_index() == fi.signature_index(), "Must be"); | |
| assert(fi_ref->offset() == fi.offset(), "Must be"); | |
| assert(fi_ref->access_flags().as_field_flags() == fi.access_flags().as_field_flags(), "Must be"); | |
| assert(fi_ref->field_flags().as_uint() == fi.field_flags().as_uint(), " Must be"); | |
| if(fi_ref->field_flags().is_initialized()) { | |
| assert(fi_ref->initializer_index() == fi.initializer_index(), "Must be"); | |
| } | |
| if (fi_ref->field_flags().is_generic()) { | |
| assert(fi_ref->generic_signature_index() == fi.generic_signature_index(), "Must be"); | |
| } | |
| if (fi_ref->field_flags().is_contended()) { | |
| assert(fi_ref->contended_group() == fi.contended_group(), "Must be"); | |
| } | |
| } | |
| #endif // ASSERT | |
| return fis; | |
| } | |
| int FieldInfoStream::compare_name_and_sig(const Symbol* n1, const Symbol* s1, const Symbol* n2, const Symbol* s2) { | |
| int cmp = n1->fast_compare(n2); | |
| return cmp != 0 ? cmp : s1->fast_compare(s2); | |
| } | |
| // We use both name and signature during the comparison; while JLS require unique | |
| // names for fields, JVMS requires only unique name + signature combination. | |
| struct field_pos { | |
| Symbol* _name; | |
| Symbol* _signature; | |
| int _index; | |
| int _position; | |
| }; | |
| class FieldInfoSupplier: public PackedTableBuilder::Supplier { | |
| const field_pos* _positions; | |
| size_t _elements; | |
| public: | |
| FieldInfoSupplier(const field_pos* positions, size_t elements): _positions(positions), _elements(elements) {} | |
| bool next(uint32_t* key, uint32_t* value) override { | |
| if (_elements == 0) { | |
| return false; | |
| } | |
| *key = _positions->_position; | |
| *value = _positions->_index; | |
| ++_positions; | |
| --_elements; | |
| return true; | |
| } | |
| }; | |
| Array<u1>* FieldInfoStream::create_search_table(ConstantPool* cp, const Array<u1>* fis, ClassLoaderData* loader_data, TRAPS) { | |
| if (CDSConfig::is_dumping_dynamic_archive()) { | |
| // We cannot use search table; in case of dynamic archives it should be sorted by "requested" addresses, | |
| // but Symbol* addresses are coming from _constants, which has "buffered" addresses. | |
| // For background, see new comments inside allocate_node_impl in symbolTable.cpp | |
| return nullptr; | |
| } | |
| FieldInfoReader r(fis); | |
| int java_fields; | |
| int injected_fields; | |
| r.read_field_counts(&java_fields, &injected_fields); | |
| assert(java_fields >= 0, "must be"); | |
| if (java_fields == 0 || fis->length() == 0 || static_cast<uint>(java_fields) < BinarySearchThreshold) { | |
| return nullptr; | |
| } | |
| ResourceMark rm; | |
| field_pos* positions = NEW_RESOURCE_ARRAY(field_pos, java_fields); | |
| for (int i = 0; i < java_fields; ++i) { | |
| assert(r.has_next(), "number of fields must match"); | |
| positions[i]._position = r.position(); | |
| FieldInfo fi; | |
| r.read_field_info(fi); | |
| positions[i]._name = fi.name(cp); | |
| positions[i]._signature = fi.signature(cp); | |
| positions[i]._index = i; | |
| } | |
| auto compare_pair = [](const void* v1, const void* v2) { | |
| const field_pos* p1 = reinterpret_cast<const field_pos*>(v1); | |
| const field_pos* p2 = reinterpret_cast<const field_pos*>(v2); | |
| return compare_name_and_sig(p1->_name, p1->_signature, p2->_name, p2->_signature); | |
| }; | |
| qsort(positions, java_fields, sizeof(field_pos), compare_pair); | |
| PackedTableBuilder builder(fis->length() - 1, java_fields - 1); | |
| Array<u1>* table = MetadataFactory::new_array<u1>(loader_data, java_fields * builder.element_bytes(), CHECK_NULL); | |
| FieldInfoSupplier supplier(positions, java_fields); | |
| builder.fill(table->data(), static_cast<size_t>(table->length()), supplier); | |
| return table; | |
| } | |
| GrowableArray<FieldInfo>* FieldInfoStream::create_FieldInfoArray(const Array<u1>* fis, int* java_fields_count, int* injected_fields_count) { | |
| FieldInfoReader r(fis); | |
| r.read_field_counts(java_fields_count, injected_fields_count); | |
| int length = *java_fields_count + *injected_fields_count; | |
| GrowableArray<FieldInfo>* array = new GrowableArray<FieldInfo>(length); | |
| while (r.has_next()) { | |
| FieldInfo fi; | |
| r.read_field_info(fi); | |
| array->append(fi); | |
| } | |
| assert(array->length() == length, "Must be"); | |
| return array; | |
| } | |
| void FieldInfoStream::print_from_fieldinfo_stream(Array<u1>* fis, outputStream* os, ConstantPool* cp) { | |
| FieldInfoReader r(fis); | |
| int java_fields_count; | |
| int injected_fields_count; | |
| r.read_field_counts(&java_fields_count, &injected_fields_count); | |
| while (r.has_next()) { | |
| FieldInfo fi; | |
| r.read_field_info(fi); | |
| fi.print(os, cp); | |
| } | |
| } | |
| class FieldInfoComparator: public PackedTableLookup::Comparator { | |
| const FieldInfoReader* _reader; | |
| ConstantPool* _cp; | |
| const Symbol* _name; | |
| const Symbol* _signature; | |
| public: | |
| FieldInfoComparator(const FieldInfoReader* reader, ConstantPool* cp, const Symbol* name, const Symbol* signature): | |
| _reader(reader), _cp(cp), _name(name), _signature(signature) {} | |
| int compare_to(uint32_t position) override { | |
| FieldInfoReader r2(*_reader); | |
| r2.set_position_and_next_index(position, -1); | |
| u2 name_index, sig_index; | |
| r2.read_name_and_signature(&name_index, &sig_index); | |
| Symbol* mid_name = _cp->symbol_at(name_index); | |
| Symbol* mid_sig = _cp->symbol_at(sig_index); | |
| return FieldInfoStream::compare_name_and_sig(_name, _signature, mid_name, mid_sig); | |
| } | |
| #ifdef ASSERT | |
| void reset(uint32_t position) override { | |
| FieldInfoReader r2(*_reader); | |
| r2.set_position_and_next_index(position, -1); | |
| u2 name_index, signature_index; | |
| r2.read_name_and_signature(&name_index, &signature_index); | |
| _name = _cp->symbol_at(name_index); | |
| _signature = _cp->symbol_at(signature_index); | |
| } | |
| #endif // ASSERT | |
| }; | |
| #ifdef ASSERT | |
| void FieldInfoStream::validate_search_table(ConstantPool* cp, const Array<u1>* fis, const Array<u1>* search_table) { | |
| if (search_table == nullptr) { | |
| return; | |
| } | |
| FieldInfoReader reader(fis); | |
| int java_fields, injected_fields; | |
| reader.read_field_counts(&java_fields, &injected_fields); | |
| assert(java_fields > 0, "must be"); | |
| PackedTableLookup lookup(fis->length() - 1, java_fields - 1, search_table); | |
| assert(lookup.element_bytes() * java_fields == static_cast<unsigned int>(search_table->length()), "size does not match"); | |
| FieldInfoComparator comparator(&reader, cp, nullptr, nullptr); | |
| // Check 1: assert that elements have the correct order based on the comparison function | |
| lookup.validate_order(comparator); | |
| // Check 2: Iterate through the original stream (not just search_table) and try if lookup works as expected | |
| reader.set_position_and_next_index(0, 0); | |
| reader.read_field_counts(&java_fields, &injected_fields); | |
| while (reader.has_next()) { | |
| int field_start = reader.position(); | |
| FieldInfo fi; | |
| reader.read_field_info(fi); | |
| if (fi.field_flags().is_injected()) { | |
| // checking only java fields that precede injected ones | |
| break; | |
| } | |
| FieldInfoReader r2(fis); | |
| int index = r2.search_table_lookup(search_table, fi.name(cp), fi.signature(cp), cp, java_fields); | |
| assert(index == static_cast<int>(fi.index()), "wrong index: %d != %u", index, fi.index()); | |
| assert(index == r2.next_index(), "index should match"); | |
| assert(field_start == r2.position(), "must find the same position"); | |
| } | |
| } | |
| #endif // ASSERT | |
| void FieldInfoStream::print_search_table(outputStream* st, ConstantPool* cp, const Array<u1>* fis, const Array<u1>* search_table) { | |
| if (search_table == nullptr) { | |
| return; | |
| } | |
| FieldInfoReader reader(fis); | |
| int java_fields, injected_fields; | |
| reader.read_field_counts(&java_fields, &injected_fields); | |
| assert(java_fields > 0, "must be"); | |
| PackedTableLookup lookup(fis->length() - 1, java_fields - 1, search_table); | |
| auto printer = [&] (size_t offset, uint32_t position, uint32_t index) { | |
| reader.set_position_and_next_index(position, -1); | |
| u2 name_index, sig_index; | |
| reader.read_name_and_signature(&name_index, &sig_index); | |
| Symbol* name = cp->symbol_at(name_index); | |
| Symbol* sig = cp->symbol_at(sig_index); | |
| st->print(" [%zu] #%d,#%d = ", offset, name_index, sig_index); | |
| name->print_symbol_on(st); | |
| st->print(":"); | |
| sig->print_symbol_on(st); | |
| st->print(" @ %p,%p", name, sig); | |
| st->cr(); | |
| }; | |
| lookup.iterate(printer); | |
| } | |
| int FieldInfoReader::search_table_lookup(const Array<u1>* search_table, const Symbol* name, const Symbol* signature, ConstantPool* cp, int java_fields) { | |
| assert(java_fields >= 0, "must be"); | |
| if (java_fields == 0) { | |
| return -1; | |
| } | |
| FieldInfoComparator comp(this, cp, name, signature); | |
| PackedTableLookup lookup(_r.limit() - 1, java_fields - 1, search_table); | |
| uint32_t position; | |
| static_assert(sizeof(uint32_t) == sizeof(_next_index), "field size assert"); | |
| if (lookup.search(comp, &position, reinterpret_cast<uint32_t*>(&_next_index))) { | |
| _r.set_position(static_cast<int>(position)); | |
| return _next_index; | |
| } else { | |
| return -1; | |
| } | |
| } |