mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
Merge remote-tracking branch 'jdk/master' into accelerated-triggers
This commit is contained in:
commit
e772e4b84d
@ -458,12 +458,10 @@ char* os::map_memory_to_file(char* base, size_t size, int fd) {
|
||||
warning("Failed mmap to file. (%s)", os::strerror(errno));
|
||||
return nullptr;
|
||||
}
|
||||
if (base != nullptr && addr != base) {
|
||||
if (!os::release_memory(addr, size)) {
|
||||
warning("Could not release memory on unsuccessful file mapping");
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The requested address should be the same as the returned address when using MAP_FIXED
|
||||
// as per POSIX.
|
||||
assert(base == nullptr || addr == base, "base should equal addr when using MAP_FIXED");
|
||||
return addr;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012, 2021 SAP SE. All rights reserved.
|
||||
* Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012, 2026 SAP SE. 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
|
||||
@ -946,7 +946,7 @@ static int create_sharedmem_file(const char* dirname, const char* filename, size
|
||||
if (result == -1 ) break;
|
||||
if (!os::write(fd, &zero_int, 1)) {
|
||||
if (errno == ENOSPC) {
|
||||
warning("Insufficient space for shared memory file:\n %s\nTry using the -Djava.io.tmpdir= option to select an alternate temp location.\n", filename);
|
||||
warning("Insufficient space for shared memory file: %s/%s\n", dirname, filename);
|
||||
}
|
||||
result = OS_ERR;
|
||||
break;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2026, 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
|
||||
@ -951,6 +951,32 @@ struct enum_sigcode_desc_t {
|
||||
const char* s_desc;
|
||||
};
|
||||
|
||||
#if defined(LINUX)
|
||||
// Additional kernel si_code definitions that are only exported by
|
||||
// more recent glibc distributions, so we have to hard-code the values.
|
||||
#ifndef BUS_MCEERR_AR // glibc 2.17
|
||||
#define BUS_MCEERR_AR 4
|
||||
#define BUS_MCEERR_AO 5
|
||||
#endif
|
||||
|
||||
#ifndef SEGV_PKUERR // glibc 2.27
|
||||
#define SEGV_PKUERR 4
|
||||
#endif
|
||||
|
||||
#ifndef SYS_SECCOMP // glibc 2.28
|
||||
#define SYS_SECCOMP 1
|
||||
#endif
|
||||
|
||||
#ifndef TRAP_BRANCH // glibc 2.30
|
||||
#define TRAP_BRANCH 3
|
||||
#endif
|
||||
|
||||
#ifndef TRAP_HWBKPT // not glibc version specific - gdb related
|
||||
#define TRAP_HWBKPT 4
|
||||
#endif
|
||||
|
||||
#endif // LINUX
|
||||
|
||||
static bool get_signal_code_description(const siginfo_t* si, enum_sigcode_desc_t* out) {
|
||||
|
||||
const struct {
|
||||
@ -976,6 +1002,7 @@ static bool get_signal_code_description(const siginfo_t* si, enum_sigcode_desc_t
|
||||
{ SIGSEGV, SEGV_ACCERR, "SEGV_ACCERR", "Invalid permissions for mapped object." },
|
||||
#if defined(LINUX)
|
||||
{ SIGSEGV, SEGV_BNDERR, "SEGV_BNDERR", "Failed address bound checks." },
|
||||
{ SIGSEGV, SEGV_PKUERR, "SEGV_PKUERR", "Protection key checking failure." },
|
||||
#endif
|
||||
#if defined(AIX)
|
||||
// no explanation found what keyerr would be
|
||||
@ -984,8 +1011,18 @@ static bool get_signal_code_description(const siginfo_t* si, enum_sigcode_desc_t
|
||||
{ SIGBUS, BUS_ADRALN, "BUS_ADRALN", "Invalid address alignment." },
|
||||
{ SIGBUS, BUS_ADRERR, "BUS_ADRERR", "Nonexistent physical address." },
|
||||
{ SIGBUS, BUS_OBJERR, "BUS_OBJERR", "Object-specific hardware error." },
|
||||
#if defined(LINUX)
|
||||
{ SIGBUS, BUS_MCEERR_AR,"BUS_MCEERR_AR","Hardware memory error consumed on a machine check: action required." },
|
||||
{ SIGBUS, BUS_MCEERR_AO,"BUS_MCEERR_AO","Hardware memory error detected in process but not consumed: action optional." },
|
||||
|
||||
{ SIGSYS, SYS_SECCOMP, "SYS_SECCOMP", "Secure computing (seccomp) filter failure." },
|
||||
#endif
|
||||
{ SIGTRAP, TRAP_BRKPT, "TRAP_BRKPT", "Process breakpoint." },
|
||||
{ SIGTRAP, TRAP_TRACE, "TRAP_TRACE", "Process trace trap." },
|
||||
#if defined(LINUX)
|
||||
{ SIGTRAP, TRAP_BRANCH, "TRAP_BRANCH", "Process taken branch trap." },
|
||||
{ SIGTRAP, TRAP_HWBKPT, "TRAP_HWBKPT", "Hardware breakpoint/watchpoint." },
|
||||
#endif
|
||||
{ SIGCHLD, CLD_EXITED, "CLD_EXITED", "Child has exited." },
|
||||
{ SIGCHLD, CLD_KILLED, "CLD_KILLED", "Child has terminated abnormally and did not create a core file." },
|
||||
{ SIGCHLD, CLD_DUMPED, "CLD_DUMPED", "Child has terminated abnormally and created a core file." },
|
||||
@ -993,6 +1030,7 @@ static bool get_signal_code_description(const siginfo_t* si, enum_sigcode_desc_t
|
||||
{ SIGCHLD, CLD_STOPPED, "CLD_STOPPED", "Child has stopped." },
|
||||
{ SIGCHLD, CLD_CONTINUED,"CLD_CONTINUED","Stopped child has continued." },
|
||||
#ifdef SIGPOLL
|
||||
{ SIGPOLL, POLL_IN, "POLL_IN", "Data input available." },
|
||||
{ SIGPOLL, POLL_OUT, "POLL_OUT", "Output buffers available." },
|
||||
{ SIGPOLL, POLL_MSG, "POLL_MSG", "Input message available." },
|
||||
{ SIGPOLL, POLL_ERR, "POLL_ERR", "I/O error." },
|
||||
|
||||
@ -62,8 +62,6 @@ jint EpsilonHeap::initialize() {
|
||||
|
||||
// Enable monitoring
|
||||
_monitoring_support = new EpsilonMonitoringSupport(this);
|
||||
_last_counter_update = 0;
|
||||
_last_heap_print = 0;
|
||||
|
||||
// Install barrier set
|
||||
BarrierSet::set_barrier_set(new EpsilonBarrierSet());
|
||||
@ -156,17 +154,17 @@ HeapWord* EpsilonHeap::allocate_work(size_t size) {
|
||||
// At this point, some diagnostic subsystems might not yet be initialized.
|
||||
// We pretend the printout happened either way. This keeps allocation path
|
||||
// from obsessively checking the subsystems' status on every allocation.
|
||||
size_t last_counter = AtomicAccess::load(&_last_counter_update);
|
||||
size_t last_counter = _last_counter_update.load_relaxed();
|
||||
if ((used - last_counter >= _step_counter_update) &&
|
||||
AtomicAccess::cmpxchg(&_last_counter_update, last_counter, used) == last_counter) {
|
||||
_last_counter_update.compare_set(last_counter, used)) {
|
||||
if (_monitoring_support->is_ready()) {
|
||||
_monitoring_support->update_counters();
|
||||
}
|
||||
}
|
||||
|
||||
size_t last_heap = AtomicAccess::load(&_last_heap_print);
|
||||
size_t last_heap = _last_heap_print.load_relaxed();
|
||||
if ((used - last_heap >= _step_heap_print) &&
|
||||
AtomicAccess::cmpxchg(&_last_heap_print, last_heap, used) == last_heap) {
|
||||
_last_heap_print.compare_set(last_heap, used)) {
|
||||
print_heap_info(used);
|
||||
if (Metaspace::initialized()) {
|
||||
print_metaspace_info();
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
#include "gc/shared/collectedHeap.hpp"
|
||||
#include "gc/shared/space.hpp"
|
||||
#include "memory/virtualspace.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "services/memoryManager.hpp"
|
||||
|
||||
class EpsilonHeap : public CollectedHeap {
|
||||
@ -45,8 +46,8 @@ private:
|
||||
size_t _step_counter_update;
|
||||
size_t _step_heap_print;
|
||||
int64_t _decay_time_ns;
|
||||
volatile size_t _last_counter_update;
|
||||
volatile size_t _last_heap_print;
|
||||
Atomic<size_t> _last_counter_update;
|
||||
Atomic<size_t> _last_heap_print;
|
||||
|
||||
void print_tracing_info() const override;
|
||||
void stop() override {};
|
||||
|
||||
@ -96,7 +96,6 @@ public:
|
||||
EpsilonMonitoringSupport::EpsilonMonitoringSupport(EpsilonHeap* heap) {
|
||||
_heap_counters = new EpsilonGenerationCounters(heap);
|
||||
_space_counters = new EpsilonSpaceCounters("Heap", 0, heap->max_capacity(), 0, _heap_counters);
|
||||
_ready = false;
|
||||
}
|
||||
|
||||
void EpsilonMonitoringSupport::update_counters() {
|
||||
@ -114,9 +113,9 @@ void EpsilonMonitoringSupport::update_counters() {
|
||||
}
|
||||
|
||||
bool EpsilonMonitoringSupport::is_ready() {
|
||||
return AtomicAccess::load_acquire(&_ready);
|
||||
return _ready.load_acquire();
|
||||
}
|
||||
|
||||
void EpsilonMonitoringSupport::mark_ready() {
|
||||
return AtomicAccess::release_store(&_ready, true);
|
||||
_ready.release_store(true);
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
#define SHARE_GC_EPSILON_EPSILONMONITORINGSUPPORT_HPP
|
||||
|
||||
#include "memory/allocation.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
|
||||
class EpsilonGenerationCounters;
|
||||
class EpsilonSpaceCounters;
|
||||
@ -35,7 +36,7 @@ class EpsilonMonitoringSupport : public CHeapObj<mtGC> {
|
||||
private:
|
||||
EpsilonGenerationCounters* _heap_counters;
|
||||
EpsilonSpaceCounters* _space_counters;
|
||||
volatile bool _ready;
|
||||
Atomic<bool> _ready;
|
||||
|
||||
public:
|
||||
EpsilonMonitoringSupport(EpsilonHeap* heap);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2001, 2026, 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
|
||||
@ -686,7 +686,8 @@ HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) {
|
||||
// the check before we do the actual allocation. The reason for doing it
|
||||
// before the allocation is that we avoid having to keep track of the newly
|
||||
// allocated memory while we do a GC.
|
||||
if (policy()->need_to_start_conc_mark("concurrent humongous allocation",
|
||||
// Only try that if we can actually perform a GC.
|
||||
if (is_init_completed() && policy()->need_to_start_conc_mark("concurrent humongous allocation",
|
||||
word_size)) {
|
||||
try_collect(word_size, GCCause::_g1_humongous_allocation, collection_counters(this));
|
||||
}
|
||||
|
||||
@ -700,7 +700,7 @@ void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) {
|
||||
// then someone else has made such a claim and the deferred update has not
|
||||
// yet been processed and will include our change, so we don't need to do
|
||||
// anything further.
|
||||
if (_deferred_updates_next.compare_exchange(nullptr, this) == nullptr) {
|
||||
if (_deferred_updates_next.compare_set(nullptr, this)) {
|
||||
// Successfully claimed. Push, with self-loop for end-of-list.
|
||||
Block* head = owner->_deferred_updates.load_relaxed();
|
||||
while (true) {
|
||||
|
||||
@ -56,7 +56,7 @@ void PretouchTask::work(uint worker_id) {
|
||||
char* cur_end = cur_start + MIN2(_chunk_size, pointer_delta(_end_addr, cur_start, 1));
|
||||
if (cur_start >= cur_end) {
|
||||
break;
|
||||
} else if (cur_start == _cur_addr.compare_exchange(cur_start, cur_end)) {
|
||||
} else if (_cur_addr.compare_set(cur_start, cur_end)) {
|
||||
os::pretouch_memory(cur_start, cur_end, _page_size);
|
||||
} // Else attempt to claim chunk failed, so try again.
|
||||
}
|
||||
|
||||
@ -183,8 +183,8 @@ protected:
|
||||
_age.store_relaxed(new_age);
|
||||
}
|
||||
|
||||
Age cmpxchg_age(Age old_age, Age new_age) {
|
||||
return _age.compare_exchange(old_age, new_age);
|
||||
bool par_set_age(Age old_age, Age new_age) {
|
||||
return _age.compare_set(old_age, new_age);
|
||||
}
|
||||
|
||||
idx_t age_top_relaxed() const {
|
||||
@ -345,7 +345,7 @@ protected:
|
||||
|
||||
using TaskQueueSuper<N, MT>::age_relaxed;
|
||||
using TaskQueueSuper<N, MT>::set_age_relaxed;
|
||||
using TaskQueueSuper<N, MT>::cmpxchg_age;
|
||||
using TaskQueueSuper<N, MT>::par_set_age;
|
||||
using TaskQueueSuper<N, MT>::age_top_relaxed;
|
||||
|
||||
using TaskQueueSuper<N, MT>::increment_index;
|
||||
|
||||
@ -170,8 +170,7 @@ bool GenericTaskQueue<E, MT, N>::pop_local_slow(uint localBot, Age oldAge) {
|
||||
if (localBot == oldAge.top()) {
|
||||
// No competing pop_global has yet incremented "top"; we'll try to
|
||||
// install new_age, thus claiming the element.
|
||||
Age tempAge = cmpxchg_age(oldAge, newAge);
|
||||
if (tempAge == oldAge) {
|
||||
if (par_set_age(oldAge, newAge)) {
|
||||
// We win.
|
||||
assert_not_underflow(localBot, age_top_relaxed());
|
||||
TASKQUEUE_STATS_ONLY(stats.record_pop_slow());
|
||||
@ -283,12 +282,12 @@ typename GenericTaskQueue<E, MT, N>::PopResult GenericTaskQueue<E, MT, N>::pop_g
|
||||
idx_t new_top = increment_index(oldAge.top());
|
||||
idx_t new_tag = oldAge.tag() + ((new_top == 0) ? 1 : 0);
|
||||
Age newAge(new_top, new_tag);
|
||||
Age resAge = cmpxchg_age(oldAge, newAge);
|
||||
bool result = par_set_age(oldAge, newAge);
|
||||
|
||||
// Note that using "bottom" here might fail, since a pop_local might
|
||||
// have decremented it.
|
||||
assert_not_underflow(localBot, newAge.top());
|
||||
return resAge == oldAge ? PopResult::Success : PopResult::Contended;
|
||||
return result ? PopResult::Success : PopResult::Contended;
|
||||
}
|
||||
|
||||
inline int randomParkAndMiller(int *seed0) {
|
||||
|
||||
@ -61,7 +61,12 @@ ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() :
|
||||
|
||||
void ShenandoahGenerationalControlThread::run_service() {
|
||||
|
||||
// This is the only instance of request. It is important that request.generation
|
||||
// does not change between a concurrent cycle failure and the start of a degenerated
|
||||
// cycle. We initialize it with the young generation to handle the pathological case
|
||||
// where the very first cycle is degenerated (some tests exercise this path).
|
||||
ShenandoahGCRequest request;
|
||||
request.generation = _heap->young_generation();
|
||||
while (!should_terminate()) {
|
||||
|
||||
// Figure out if we have pending requests.
|
||||
@ -77,12 +82,10 @@ void ShenandoahGenerationalControlThread::run_service() {
|
||||
|
||||
// If the cycle was cancelled, continue the next iteration to deal with it. Otherwise,
|
||||
// if there was no other cycle requested, cleanup and wait for the next request.
|
||||
if (!_heap->cancelled_gc()) {
|
||||
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
|
||||
if (_requested_gc_cause == GCCause::_no_gc) {
|
||||
set_gc_mode(ml, none);
|
||||
ml.wait();
|
||||
}
|
||||
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
|
||||
if (_requested_gc_cause == GCCause::_no_gc) {
|
||||
set_gc_mode(ml, none);
|
||||
ml.wait();
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,8 +99,7 @@ void ShenandoahGenerationalControlThread::stop_service() {
|
||||
log_debug(gc, thread)("Stopping control thread");
|
||||
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
|
||||
_heap->cancel_gc(GCCause::_shenandoah_stop_vm);
|
||||
_requested_gc_cause = GCCause::_shenandoah_stop_vm;
|
||||
notify_cancellation(ml, GCCause::_shenandoah_stop_vm);
|
||||
notify_control_thread(ml, GCCause::_shenandoah_stop_vm);
|
||||
// We can't wait here because it may interfere with the active cycle's ability
|
||||
// to reach a safepoint (this runs on a java thread).
|
||||
}
|
||||
@ -105,29 +107,39 @@ void ShenandoahGenerationalControlThread::stop_service() {
|
||||
void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest& request) {
|
||||
// Hold the lock while we read request cause and generation
|
||||
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
|
||||
if (_heap->cancelled_gc()) {
|
||||
// The previous request was cancelled. Either it was cancelled for an allocation
|
||||
// failure (degenerated cycle), or old marking was cancelled to run a young collection.
|
||||
// In either case, the correct generation for the next cycle can be determined by
|
||||
// the cancellation cause.
|
||||
request.cause = _heap->clear_cancellation(GCCause::_shenandoah_concurrent_gc);
|
||||
if (request.cause == GCCause::_shenandoah_concurrent_gc) {
|
||||
|
||||
log_debug(gc, thread)("cancelled cause: %s, requested cause: %s",
|
||||
GCCause::to_string(_heap->cancelled_cause()), GCCause::to_string(_requested_gc_cause));
|
||||
|
||||
request.cause = _requested_gc_cause;
|
||||
if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) {
|
||||
if (_degen_point == ShenandoahGC::_degenerated_unset) {
|
||||
request.generation = _heap->young_generation();
|
||||
_degen_point = ShenandoahGC::_degenerated_outside_cycle;
|
||||
} else {
|
||||
assert(request.generation != nullptr, "Must know which generation to use for degenerated cycle");
|
||||
}
|
||||
} else {
|
||||
request.cause = _requested_gc_cause;
|
||||
if (request.cause == GCCause::_shenandoah_concurrent_gc) {
|
||||
// This is a regulator request. It is also possible that the regulator "canceled" an old mark,
|
||||
// so we can clear that here. This clear operation will only clear the cancellation if it is
|
||||
// a regulator request.
|
||||
_heap->clear_cancellation(GCCause::_shenandoah_concurrent_gc);
|
||||
}
|
||||
request.generation = _requested_generation;
|
||||
|
||||
// Only clear these if we made a request from them. In the case of a cancelled gc,
|
||||
// we do not want to inadvertently lose this pending request.
|
||||
_requested_gc_cause = GCCause::_no_gc;
|
||||
_requested_generation = nullptr;
|
||||
}
|
||||
|
||||
log_debug(gc, thread)("request.cause: %s, request.generation: %s",
|
||||
GCCause::to_string(request.cause), request.generation == nullptr ? "None" : request.generation->name());
|
||||
|
||||
_requested_gc_cause = GCCause::_no_gc;
|
||||
_requested_generation = nullptr;
|
||||
|
||||
if (request.cause == GCCause::_no_gc || request.cause == GCCause::_shenandoah_stop_vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(request.generation != nullptr, "request.generation cannot be null, cause is: %s", GCCause::to_string(request.cause));
|
||||
GCMode mode;
|
||||
if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) {
|
||||
mode = prepare_for_allocation_failure_gc(request);
|
||||
@ -140,11 +152,9 @@ void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest&
|
||||
}
|
||||
|
||||
ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_allocation_failure_gc(ShenandoahGCRequest &request) {
|
||||
|
||||
if (_degen_point == ShenandoahGC::_degenerated_unset) {
|
||||
_degen_point = ShenandoahGC::_degenerated_outside_cycle;
|
||||
request.generation = _heap->young_generation();
|
||||
} else if (request.generation->is_old()) {
|
||||
// Important: not all paths update the request.generation. This is intentional.
|
||||
// A degenerated cycle must use the same generation carried over from the previous request.
|
||||
if (request.generation->is_old()) {
|
||||
// This means we degenerated during the young bootstrap for the old generation
|
||||
// cycle. The following degenerated cycle should therefore also be young.
|
||||
request.generation = _heap->young_generation();
|
||||
@ -588,6 +598,8 @@ bool ShenandoahGenerationalControlThread::check_cancellation_or_degen(Shenandoah
|
||||
if (ShenandoahCollectorPolicy::is_allocation_failure(_heap->cancelled_cause())) {
|
||||
assert(_degen_point == ShenandoahGC::_degenerated_unset,
|
||||
"Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point));
|
||||
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
|
||||
_requested_gc_cause = _heap->cancelled_cause();
|
||||
_degen_point = point;
|
||||
log_debug(gc, thread)("Cancellation detected:, reason: %s, degen point: %s",
|
||||
GCCause::to_string(_heap->cancelled_cause()),
|
||||
@ -634,9 +646,7 @@ void ShenandoahGenerationalControlThread::service_stw_degenerated_cycle(const Sh
|
||||
|
||||
void ShenandoahGenerationalControlThread::request_gc(GCCause::Cause cause) {
|
||||
if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) {
|
||||
// GC should already be cancelled. Here we are just notifying the control thread to
|
||||
// wake up and handle the cancellation request, so we don't need to set _requested_gc_cause.
|
||||
notify_cancellation(cause);
|
||||
notify_control_thread(cause);
|
||||
} else if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) {
|
||||
handle_requested_gc(cause);
|
||||
}
|
||||
@ -654,7 +664,8 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera
|
||||
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
|
||||
if (gc_mode() == servicing_old) {
|
||||
if (!preempt_old_marking(generation)) {
|
||||
log_debug(gc, thread)("Cannot start young, old collection is not preemptible");
|
||||
// Global should be able to cause old collection to be abandoned
|
||||
log_debug(gc, thread)("Cannot start %s, old collection is not preemptible", generation->name());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -662,7 +673,7 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera
|
||||
log_info(gc)("Preempting old generation mark to allow %s GC", generation->name());
|
||||
while (gc_mode() == servicing_old) {
|
||||
ShenandoahHeap::heap()->cancel_gc(GCCause::_shenandoah_concurrent_gc);
|
||||
notify_cancellation(ml, GCCause::_shenandoah_concurrent_gc);
|
||||
notify_control_thread(ml, GCCause::_shenandoah_concurrent_gc, generation);
|
||||
ml.wait();
|
||||
}
|
||||
return true;
|
||||
@ -696,21 +707,34 @@ void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause c
|
||||
|
||||
void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation) {
|
||||
assert(_control_lock.is_locked(), "Request lock must be held here");
|
||||
log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name());
|
||||
_requested_gc_cause = cause;
|
||||
_requested_generation = generation;
|
||||
ml.notify();
|
||||
if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) {
|
||||
// We have already observed a request to handle an allocation failure. We cannot allow
|
||||
// another request (System.gc or regulator) to subvert the degenerated cycle.
|
||||
log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause));
|
||||
} else {
|
||||
log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name());
|
||||
_requested_gc_cause = cause;
|
||||
_requested_generation = generation;
|
||||
ml.notify();
|
||||
}
|
||||
}
|
||||
|
||||
void ShenandoahGenerationalControlThread::notify_cancellation(GCCause::Cause cause) {
|
||||
void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause) {
|
||||
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
|
||||
notify_cancellation(ml, cause);
|
||||
notify_control_thread(ml, cause);
|
||||
}
|
||||
|
||||
void ShenandoahGenerationalControlThread::notify_cancellation(MonitorLocker& ml, GCCause::Cause cause) {
|
||||
assert(_heap->cancelled_gc(), "GC should already be cancelled");
|
||||
log_debug(gc,thread)("Notify control (%s): %s", gc_mode_name(gc_mode()), GCCause::to_string(cause));
|
||||
ml.notify();
|
||||
void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause) {
|
||||
assert(_control_lock.is_locked(), "Request lock must be held here");
|
||||
if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) {
|
||||
// We have already observed a request to handle an allocation failure. We cannot allow
|
||||
// another request (System.gc or regulator) to subvert the degenerated cycle.
|
||||
log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause));
|
||||
} else {
|
||||
log_debug(gc, thread)("Notify control (%s): %s", gc_mode_name(gc_mode()), GCCause::to_string(cause));
|
||||
_requested_gc_cause = cause;
|
||||
ml.notify();
|
||||
}
|
||||
}
|
||||
|
||||
bool ShenandoahGenerationalControlThread::preempt_old_marking(ShenandoahGeneration* generation) {
|
||||
|
||||
@ -135,16 +135,13 @@ private:
|
||||
// Return printable name for the given gc mode.
|
||||
static const char* gc_mode_name(GCMode mode);
|
||||
|
||||
// Takes the request lock and updates the requested cause and generation, then notifies the control thread.
|
||||
// The overloaded variant should be used when the _control_lock is already held.
|
||||
// These notify the control thread after updating _requested_gc_cause and (optionally) _requested_generation.
|
||||
// Updating the requested generation is not necessary for allocation failures nor when stopping the thread.
|
||||
void notify_control_thread(GCCause::Cause cause);
|
||||
void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause);
|
||||
void notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation);
|
||||
void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation);
|
||||
|
||||
// Notifies the control thread, but does not update the requested cause or generation.
|
||||
// The overloaded variant should be used when the _control_lock is already held.
|
||||
void notify_cancellation(GCCause::Cause cause);
|
||||
void notify_cancellation(MonitorLocker& ml, GCCause::Cause cause);
|
||||
|
||||
// Configure the heap to age objects and regions if the aging period has elapsed.
|
||||
void maybe_set_aging_cycle();
|
||||
|
||||
|
||||
@ -45,13 +45,13 @@ public:
|
||||
void post_initialize_heuristics() override;
|
||||
|
||||
static ShenandoahGenerationalHeap* heap() {
|
||||
assert(ShenandoahCardBarrier, "Should have card barrier to use genenrational heap");
|
||||
assert(ShenandoahCardBarrier, "Should have card barrier to use generational heap");
|
||||
CollectedHeap* heap = Universe::heap();
|
||||
return cast(heap);
|
||||
}
|
||||
|
||||
static ShenandoahGenerationalHeap* cast(CollectedHeap* heap) {
|
||||
assert(ShenandoahCardBarrier, "Should have card barrier to use genenrational heap");
|
||||
assert(ShenandoahCardBarrier, "Should have card barrier to use generational heap");
|
||||
return checked_cast<ShenandoahGenerationalHeap*>(heap);
|
||||
}
|
||||
|
||||
|
||||
@ -152,6 +152,13 @@ bool ShenandoahRegulatorThread::start_global_cycle() const {
|
||||
|
||||
bool ShenandoahRegulatorThread::request_concurrent_gc(ShenandoahGeneration* generation) const {
|
||||
double now = os::elapsedTime();
|
||||
|
||||
// This call may find the control thread waiting on workers which have suspended
|
||||
// to allow a safepoint to run. If this regulator thread does not yield, the safepoint
|
||||
// will not run. The worker threads won't progress, the control thread won't progress,
|
||||
// and the regulator thread may never yield. Therefore, we leave the suspendible
|
||||
// thread set before making this call.
|
||||
SuspendibleThreadSetLeaver leaver;
|
||||
bool accepted = _control_thread->request_concurrent_gc(generation);
|
||||
if (LogTarget(Debug, gc, thread)::is_enabled() && accepted) {
|
||||
double wait_time = os::elapsedTime() - now;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012, 2026, 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
|
||||
@ -351,15 +351,22 @@ bool JfrSamplerThread::sample_native_thread(JavaThread* jt) {
|
||||
// outside the safepoint protocol.
|
||||
|
||||
// OrderAccess::fence() as part of acquiring the lock prevents loads from floating up.
|
||||
JfrMutexTryLock threads_lock(Threads_lock);
|
||||
JfrMutexTryLock lock(Threads_lock);
|
||||
|
||||
if (!threads_lock.acquired() || !jt->has_last_Java_frame()) {
|
||||
if (!lock.acquired()) {
|
||||
// Remove the native sample request and release the potentially waiting thread.
|
||||
JfrSampleMonitor jsm(tl);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (jt->thread_state() != _thread_in_native) {
|
||||
// Separate the arming of the poll (above) from the reading of JavaThread state (below).
|
||||
if (UseSystemMemoryBarrier) {
|
||||
SystemMemoryBarrier::emit();
|
||||
} else {
|
||||
OrderAccess::fence();
|
||||
}
|
||||
|
||||
if (jt->thread_state() != _thread_in_native || !jt->has_last_Java_frame()) {
|
||||
assert_lock_strong(Threads_lock);
|
||||
JfrSampleMonitor jsm(tl);
|
||||
if (jsm.is_waiting()) {
|
||||
|
||||
@ -1112,8 +1112,6 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) {
|
||||
if( !ti->is_con() ) return nullptr;
|
||||
jint con = ti->get_con();
|
||||
|
||||
Node *hook = new Node(1);
|
||||
|
||||
// First, special check for modulo 2^k-1
|
||||
if( con >= 0 && con < max_jint && is_power_of_2(con+1) ) {
|
||||
uint k = exact_log2(con+1); // Extract k
|
||||
@ -1129,7 +1127,9 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) {
|
||||
Node *x = in(1); // Value being mod'd
|
||||
Node *divisor = in(2); // Also is mask
|
||||
|
||||
hook->init_req(0, x); // Add a use to x to prevent him from dying
|
||||
// Add a use to x to prevent it from dying
|
||||
Node* hook = new Node(1);
|
||||
hook->init_req(0, x);
|
||||
// Generate code to reduce X rapidly to nearly 2^k-1.
|
||||
for( int i = 0; i < trip_count; i++ ) {
|
||||
Node *xl = phase->transform( new AndINode(x,divisor) );
|
||||
@ -1185,6 +1185,7 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) {
|
||||
}
|
||||
|
||||
// Save in(1) so that it cannot be changed or deleted
|
||||
Node* hook = new Node(1);
|
||||
hook->init_req(0, in(1));
|
||||
|
||||
// Divide using the transform from DivI to MulL
|
||||
@ -1407,8 +1408,6 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
|
||||
if( !tl->is_con() ) return nullptr;
|
||||
jlong con = tl->get_con();
|
||||
|
||||
Node *hook = new Node(1);
|
||||
|
||||
// Expand mod
|
||||
if(con >= 0 && con < max_jlong && is_power_of_2(con + 1)) {
|
||||
uint k = log2i_exact(con + 1); // Extract k
|
||||
@ -1426,13 +1425,15 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
|
||||
Node *x = in(1); // Value being mod'd
|
||||
Node *divisor = in(2); // Also is mask
|
||||
|
||||
hook->init_req(0, x); // Add a use to x to prevent him from dying
|
||||
// Add a use to x to prevent it from dying
|
||||
Node* hook = new Node(1);
|
||||
hook->init_req(0, x);
|
||||
// Generate code to reduce X rapidly to nearly 2^k-1.
|
||||
for( int i = 0; i < trip_count; i++ ) {
|
||||
Node *xl = phase->transform( new AndLNode(x,divisor) );
|
||||
Node *xh = phase->transform( new RShiftLNode(x,phase->intcon(k)) ); // Must be signed
|
||||
x = phase->transform( new AddLNode(xh,xl) );
|
||||
hook->set_req(0, x); // Add a use to x to prevent him from dying
|
||||
hook->set_req(0, x); // Add a use to x to prevent it from dying
|
||||
}
|
||||
|
||||
// Generate sign-fixup code. Was original value positive?
|
||||
@ -1482,6 +1483,8 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
|
||||
}
|
||||
|
||||
// Save in(1) so that it cannot be changed or deleted
|
||||
// Add a use to x to prevent him from dying
|
||||
Node* hook = new Node(1);
|
||||
hook->init_req(0, in(1));
|
||||
|
||||
// Divide using the transform from DivL to MulL
|
||||
|
||||
@ -3913,7 +3913,6 @@ const Type* SCMemProjNode::Value(PhaseGVN* phase) const
|
||||
LoadStoreNode::LoadStoreNode( Node *c, Node *mem, Node *adr, Node *val, const TypePtr* at, const Type* rt, uint required )
|
||||
: Node(required),
|
||||
_type(rt),
|
||||
_adr_type(at),
|
||||
_barrier_data(0)
|
||||
{
|
||||
init_req(MemNode::Control, c );
|
||||
@ -3921,6 +3920,7 @@ LoadStoreNode::LoadStoreNode( Node *c, Node *mem, Node *adr, Node *val, const Ty
|
||||
init_req(MemNode::Address, adr);
|
||||
init_req(MemNode::ValueIn, val);
|
||||
init_class_id(Class_LoadStore);
|
||||
DEBUG_ONLY(_adr_type = at; adr_type();)
|
||||
}
|
||||
|
||||
//------------------------------Value-----------------------------------------
|
||||
@ -3944,6 +3944,11 @@ const Type* LoadStoreNode::Value(PhaseGVN* phase) const {
|
||||
return bottom_type();
|
||||
}
|
||||
|
||||
const TypePtr* LoadStoreNode::adr_type() const {
|
||||
const TypePtr* cross_check = DEBUG_ONLY(_adr_type) NOT_DEBUG(nullptr);
|
||||
return MemNode::calculate_adr_type(in(MemNode::Address)->bottom_type(), cross_check);
|
||||
}
|
||||
|
||||
uint LoadStoreNode::ideal_reg() const {
|
||||
return _type->ideal_reg();
|
||||
}
|
||||
|
||||
@ -797,11 +797,6 @@ public:
|
||||
virtual int Opcode() const;
|
||||
virtual bool is_CFG() const { return false; }
|
||||
virtual const Type *bottom_type() const {return Type::MEMORY;}
|
||||
virtual const TypePtr *adr_type() const {
|
||||
Node* ctrl = in(0);
|
||||
if (ctrl == nullptr) return nullptr; // node is dead
|
||||
return ctrl->in(MemNode::Memory)->adr_type();
|
||||
}
|
||||
virtual uint ideal_reg() const { return 0;} // memory projections don't have a register
|
||||
virtual const Type* Value(PhaseGVN* phase) const;
|
||||
#ifndef PRODUCT
|
||||
@ -814,9 +809,11 @@ public:
|
||||
class LoadStoreNode : public Node {
|
||||
private:
|
||||
const Type* const _type; // What kind of value is loaded?
|
||||
const TypePtr* _adr_type; // What kind of memory is being addressed?
|
||||
uint8_t _barrier_data; // Bit field with barrier information
|
||||
virtual uint size_of() const; // Size is bigger
|
||||
#ifdef ASSERT
|
||||
const TypePtr* _adr_type; // What kind of memory is being addressed?
|
||||
#endif // ASSERT
|
||||
public:
|
||||
LoadStoreNode( Node *c, Node *mem, Node *adr, Node *val, const TypePtr* at, const Type* rt, uint required );
|
||||
virtual bool depends_only_on_test() const { return false; }
|
||||
@ -824,7 +821,7 @@ public:
|
||||
|
||||
virtual const Type *bottom_type() const { return _type; }
|
||||
virtual uint ideal_reg() const;
|
||||
virtual const class TypePtr *adr_type() const { return _adr_type; } // returns bottom_type of address
|
||||
virtual const TypePtr* adr_type() const;
|
||||
virtual const Type* Value(PhaseGVN* phase) const;
|
||||
|
||||
bool result_not_used() const;
|
||||
|
||||
@ -75,6 +75,7 @@
|
||||
// v.release_store(x) -> void
|
||||
// v.release_store_fence(x) -> void
|
||||
// v.compare_exchange(x, y [, o]) -> T
|
||||
// v.compare_set(x, y [, o]) -> bool
|
||||
// v.exchange(x [, o]) -> T
|
||||
//
|
||||
// (2) All atomic types are default constructible.
|
||||
@ -267,6 +268,11 @@ public:
|
||||
return AtomicAccess::cmpxchg(value_ptr(), compare_value, new_value, order);
|
||||
}
|
||||
|
||||
bool compare_set(T compare_value, T new_value,
|
||||
atomic_memory_order order = memory_order_conservative) {
|
||||
return compare_exchange(compare_value, new_value, order) == compare_value;
|
||||
}
|
||||
|
||||
T exchange(T new_value,
|
||||
atomic_memory_order order = memory_order_conservative) {
|
||||
return AtomicAccess::xchg(this->value_ptr(), new_value, order);
|
||||
@ -479,6 +485,13 @@ public:
|
||||
order));
|
||||
}
|
||||
|
||||
bool compare_set(T compare_value, T new_value,
|
||||
atomic_memory_order order = memory_order_conservative) {
|
||||
return _value.compare_set(decay(compare_value),
|
||||
decay(new_value),
|
||||
order);
|
||||
}
|
||||
|
||||
T exchange(T new_value, atomic_memory_order order = memory_order_conservative) {
|
||||
return recover(_value.exchange(decay(new_value), order));
|
||||
}
|
||||
|
||||
@ -157,7 +157,7 @@ inline bool ConcurrentHashTable<CONFIG, MT>::
|
||||
if (is_locked()) {
|
||||
return false;
|
||||
}
|
||||
if (_first.compare_exchange(expect, node) == expect) {
|
||||
if (_first.compare_set(expect, node)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -172,7 +172,7 @@ inline bool ConcurrentHashTable<CONFIG, MT>::
|
||||
}
|
||||
// We will expect a clean first pointer.
|
||||
Node* tmp = first();
|
||||
if (_first.compare_exchange(tmp, set_state(tmp, STATE_LOCK_BIT)) == tmp) {
|
||||
if (_first.compare_set(tmp, set_state(tmp, STATE_LOCK_BIT))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -181,7 +181,7 @@ void GenericWaitBarrier::Cell::disarm(int32_t expected_tag) {
|
||||
tag, waiters);
|
||||
|
||||
int64_t new_state = encode(0, waiters);
|
||||
if (_state.compare_exchange(state, new_state) == state) {
|
||||
if (_state.compare_set(state, new_state)) {
|
||||
// Successfully disarmed.
|
||||
break;
|
||||
}
|
||||
@ -218,7 +218,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) {
|
||||
tag, waiters);
|
||||
|
||||
int64_t new_state = encode(tag, waiters + 1);
|
||||
if (_state.compare_exchange(state, new_state) == state) {
|
||||
if (_state.compare_set(state, new_state)) {
|
||||
// Success! Proceed to wait.
|
||||
break;
|
||||
}
|
||||
@ -247,7 +247,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) {
|
||||
tag, waiters);
|
||||
|
||||
int64_t new_state = encode(tag, waiters - 1);
|
||||
if (_state.compare_exchange(state, new_state) == state) {
|
||||
if (_state.compare_set(state, new_state)) {
|
||||
// Success!
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2045,19 +2045,26 @@ public final class String
|
||||
return encode(Charset.defaultCharset(), coder(), value);
|
||||
}
|
||||
|
||||
boolean bytesCompatible(Charset charset) {
|
||||
boolean bytesCompatible(Charset charset, int srcIndex, int numChars) {
|
||||
if (isLatin1()) {
|
||||
if (charset == ISO_8859_1.INSTANCE) {
|
||||
return true; // ok, same encoding
|
||||
} else if (charset == UTF_8.INSTANCE || charset == US_ASCII.INSTANCE) {
|
||||
return !StringCoding.hasNegatives(value, 0, value.length); // ok, if ASCII-compatible
|
||||
return !StringCoding.hasNegatives(value, srcIndex, numChars); // ok, if ASCII-compatible
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void copyToSegmentRaw(MemorySegment segment, long offset) {
|
||||
MemorySegment.copy(value, 0, segment, ValueLayout.JAVA_BYTE, offset, value.length);
|
||||
void copyToSegmentRaw(MemorySegment segment, long offset, int srcIndex, int srcLength) {
|
||||
if (!isLatin1()) {
|
||||
// This method is intended to be used together with bytesCompatible, which currently only supports
|
||||
// latin1 strings. In the future, bytesCompatible could be updated to handle more cases, like
|
||||
// UTF-16 strings (when the platform and charset endianness match, and the String doesn’t contain
|
||||
// unpaired surrogates). If that happens, copyToSegmentRaw should also be updated.
|
||||
throw new IllegalStateException("This string does not support copyToSegmentRaw");
|
||||
}
|
||||
MemorySegment.copy(value, srcIndex, segment, ValueLayout.JAVA_BYTE, offset, srcLength);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -2331,13 +2331,13 @@ public final class System {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyToSegmentRaw(String string, MemorySegment segment, long offset) {
|
||||
string.copyToSegmentRaw(segment, offset);
|
||||
public void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength) {
|
||||
string.copyToSegmentRaw(segment, offset, srcIndex, srcLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean bytesCompatible(String string, Charset charset) {
|
||||
return string.bytesCompatible(charset);
|
||||
public boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars) {
|
||||
return string.bytesCompatible(charset, srcIndex, numChars);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -31,6 +31,8 @@ import java.lang.classfile.Instruction;
|
||||
import java.lang.classfile.Opcode;
|
||||
|
||||
import jdk.internal.classfile.impl.AbstractInstruction;
|
||||
import jdk.internal.classfile.impl.BytecodeHelpers;
|
||||
import jdk.internal.classfile.impl.Util;
|
||||
|
||||
/**
|
||||
* Models a local variable increment instruction in the {@code code} array of a
|
||||
@ -82,6 +84,37 @@ public sealed interface IncrementInstruction extends Instruction
|
||||
* @throws IllegalArgumentException if {@code slot} or {@code constant} is out of range
|
||||
*/
|
||||
static IncrementInstruction of(int slot, int constant) {
|
||||
return new AbstractInstruction.UnboundIncrementInstruction(slot, constant);
|
||||
var opcode = BytecodeHelpers.validateAndIsWideIinc(slot, constant) ? Opcode.IINC_W: Opcode.IINC;
|
||||
return new AbstractInstruction.UnboundIncrementInstruction(opcode, slot, constant);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return an increment instruction}
|
||||
* <p>
|
||||
* {@code slot} must be {@link java.lang.classfile##u1 u1} and
|
||||
* {@code constant} must be within {@code [-128, 127]} for
|
||||
* {@link Opcode#IINC iinc}, or {@code slot} must be
|
||||
* {@link java.lang.classfile##u2 u2} and {@code constant} must be
|
||||
* within {@code [-32768, 32767]} for {@link Opcode#IINC_W wide iinc}.
|
||||
*
|
||||
* @apiNote
|
||||
* The explicit {@code op} argument allows creating {@code wide} or
|
||||
* regular increment instructions when {@code slot} and
|
||||
* {@code constant} can be encoded with more optimized
|
||||
* increment instructions.
|
||||
*
|
||||
* @param op the opcode for the specific type of increment instruction,
|
||||
* which must be of kind {@link Opcode.Kind#INCREMENT}
|
||||
* @param slot the local variable slot to increment
|
||||
* @param constant the increment constant
|
||||
* @throws IllegalArgumentException if the opcode kind is not
|
||||
* {@link Opcode.Kind#INCREMENT} or {@code slot} or
|
||||
* {@code constant} is out of range
|
||||
* @since 27
|
||||
*/
|
||||
static IncrementInstruction of(Opcode op, int slot, int constant) {
|
||||
Util.checkKind(op, Opcode.Kind.INCREMENT);
|
||||
BytecodeHelpers.validateIncrement(op, slot, constant);
|
||||
return new AbstractInstruction.UnboundIncrementInstruction(op, slot, constant);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1296,12 +1296,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
|
||||
* over the decoding process is required.
|
||||
* <p>
|
||||
* Getting a string from a segment with a known byte offset and
|
||||
* known byte length can be done like so:
|
||||
* {@snippet lang=java :
|
||||
* byte[] bytes = new byte[length];
|
||||
* MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, length);
|
||||
* return new String(bytes, charset);
|
||||
* }
|
||||
* known byte length can be done using {@link #getString(long, Charset, long)}.
|
||||
*
|
||||
* @param offset offset in bytes (relative to this segment address) at which this
|
||||
* access operation will occur
|
||||
@ -1328,6 +1323,41 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
|
||||
*/
|
||||
String getString(long offset, Charset charset);
|
||||
|
||||
/**
|
||||
* Reads a string from this segment at the given offset, using the provided length
|
||||
* and charset.
|
||||
* <p>
|
||||
* This method always replaces malformed-input and unmappable-character
|
||||
* sequences with this charset's default replacement string. The {@link
|
||||
* java.nio.charset.CharsetDecoder} class should be used when more control
|
||||
* over the decoding process is required.
|
||||
* <p>
|
||||
* If the string contains any {@code '\0'} characters, they will be read as well.
|
||||
* This differs from {@link #getString(long, Charset)}, which will only read up
|
||||
* to the first {@code '\0'}, resulting in truncation for string data that contains
|
||||
* the {@code '\0'} character.
|
||||
*
|
||||
* @param offset offset in bytes (relative to this segment address) at which this
|
||||
* access operation will occur
|
||||
* @param charset the charset used to {@linkplain Charset#newDecoder() decode} the
|
||||
* string bytes
|
||||
* @param byteLength length, in bytes, of the region of memory to read and decode into
|
||||
* a string
|
||||
* @return a Java string constructed from the bytes read from the given starting
|
||||
* address up to the given length
|
||||
* @throws IllegalArgumentException if the size of the string is greater than the
|
||||
* largest string supported by the platform
|
||||
* @throws IndexOutOfBoundsException if {@code offset < 0}
|
||||
* @throws IndexOutOfBoundsException if {@code offset > byteSize() - byteLength}
|
||||
* @throws IllegalStateException if the {@linkplain #scope() scope} associated with
|
||||
* this segment is not {@linkplain Scope#isAlive() alive}
|
||||
* @throws WrongThreadException if this method is called from a thread {@code T},
|
||||
* such that {@code isAccessibleBy(T) == false}
|
||||
* @throws IllegalArgumentException if {@code byteLength < 0}
|
||||
* @since 27
|
||||
*/
|
||||
String getString(long offset, Charset charset, long byteLength);
|
||||
|
||||
/**
|
||||
* Writes the given string into this segment at the given offset, converting it to
|
||||
* a null-terminated byte sequence using the {@linkplain StandardCharsets#UTF_8 UTF-8}
|
||||
@ -1366,7 +1396,8 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
|
||||
* If the given string contains any {@code '\0'} characters, they will be
|
||||
* copied as well. This means that, depending on the method used to read
|
||||
* the string, such as {@link MemorySegment#getString(long)}, the string
|
||||
* will appear truncated when read again.
|
||||
* will appear truncated when read again. The string can be read without
|
||||
* truncation using {@link #getString(long, Charset, long)}.
|
||||
*
|
||||
* @param offset offset in bytes (relative to this segment address) at which this
|
||||
* access operation will occur, the final address of this write
|
||||
@ -2606,6 +2637,51 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
|
||||
elementCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the byte sequence of the given string encoded using the provided charset
|
||||
* to the destination segment.
|
||||
* <p>
|
||||
* This method always replaces malformed-input and unmappable-character
|
||||
* sequences with this charset's default replacement string. The {@link
|
||||
* java.nio.charset.CharsetDecoder} class should be used when more control
|
||||
* over the decoding process is required.
|
||||
* <p>
|
||||
* If the given string contains any {@code '\0'} characters, they will be
|
||||
* copied as well. This means that, depending on the method used to read
|
||||
* the string, such as {@link MemorySegment#getString(long)}, the string
|
||||
* will appear truncated when read again. The string can be read without
|
||||
* truncation using {@link #getString(long, Charset, long)}.
|
||||
*
|
||||
* @param src the Java string to be written into the destination segment
|
||||
* @param dstEncoding the charset used to {@linkplain Charset#newEncoder() encode}
|
||||
* the string bytes.
|
||||
* @param srcIndex the starting character index of the source string
|
||||
* @param dst the destination segment
|
||||
* @param dstOffset the starting offset, in bytes, of the destination segment
|
||||
* @param numChars the number of characters to be copied
|
||||
* @throws IllegalStateException if the {@linkplain #scope() scope} associated with
|
||||
* {@code dst} is not {@linkplain Scope#isAlive() alive}
|
||||
* @throws WrongThreadException if this method is called from a thread {@code T},
|
||||
* such that {@code dst.isAccessibleBy(T) == false}
|
||||
* @throws IndexOutOfBoundsException if either {@code srcIndex}, {@code numChars}, or {@code dstOffset}
|
||||
* are {@code < 0}
|
||||
* @throws IndexOutOfBoundsException if {@code srcIndex > src.length() - numChars}
|
||||
* @throws IllegalArgumentException if {@code dst} is {@linkplain #isReadOnly() read-only}
|
||||
* @throws IndexOutOfBoundsException if {@code dstOffset > dstSegment.byteSize() - B} where {@code B} is the size,
|
||||
* in bytes, of the substring of {@code src} encoded using the given charset
|
||||
* @return the number of copied bytes.
|
||||
* @since 27
|
||||
*/
|
||||
@ForceInline
|
||||
static long copy(String src, Charset dstEncoding, int srcIndex, MemorySegment dst, long dstOffset, int numChars) {
|
||||
Objects.requireNonNull(src);
|
||||
Objects.requireNonNull(dstEncoding);
|
||||
Objects.requireNonNull(dst);
|
||||
Objects.checkFromIndexSize(srcIndex, numChars, src.length());
|
||||
|
||||
return AbstractMemorySegmentImpl.copy(src, dstEncoding, srcIndex, dst, dstOffset, numChars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the relative offset, in bytes, of the first mismatch between the
|
||||
* source and the destination segments. More specifically, the bytes at offset
|
||||
|
||||
@ -111,7 +111,8 @@ public interface SegmentAllocator {
|
||||
* If the given string contains any {@code '\0'} characters, they will be
|
||||
* copied as well. This means that, depending on the method used to read
|
||||
* the string, such as {@link MemorySegment#getString(long)}, the string
|
||||
* will appear truncated when read again.
|
||||
* will appear truncated when read again. The string can be read without
|
||||
* truncation using {@link MemorySegment#getString(long, Charset, long)}.
|
||||
*
|
||||
* @param str the Java string to be converted into a C string
|
||||
* @param charset the charset used to {@linkplain Charset#newEncoder() encode} the
|
||||
@ -137,10 +138,10 @@ public interface SegmentAllocator {
|
||||
int termCharSize = StringSupport.CharsetKind.of(charset).terminatorCharSize();
|
||||
MemorySegment segment;
|
||||
int length;
|
||||
if (StringSupport.bytesCompatible(str, charset)) {
|
||||
if (StringSupport.bytesCompatible(str, charset, 0, str.length())) {
|
||||
length = str.length();
|
||||
segment = allocateNoInit((long) length + termCharSize);
|
||||
StringSupport.copyToSegmentRaw(str, segment, 0);
|
||||
StringSupport.copyToSegmentRaw(str, segment, 0, 0, str.length());
|
||||
} else {
|
||||
byte[] bytes = str.getBytes(charset);
|
||||
length = bytes.length;
|
||||
@ -153,6 +154,54 @@ public interface SegmentAllocator {
|
||||
return segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a Java string using the provided charset and stores the resulting
|
||||
* byte array into a memory segment.
|
||||
* <p>
|
||||
* This method always replaces malformed-input and unmappable-character
|
||||
* sequences with this charset's default replacement byte array. The
|
||||
* {@link java.nio.charset.CharsetEncoder} class should be used when more
|
||||
* control over the encoding process is required.
|
||||
* <p>
|
||||
* If the given string contains any {@code '\0'} characters, they will be
|
||||
* copied as well. This means that, depending on the method used to read
|
||||
* the string, such as {@link MemorySegment#getString(long)}, the string
|
||||
* will appear truncated when read again. The string can be read without
|
||||
* truncation using {@link MemorySegment#getString(long, Charset, long)}.
|
||||
*
|
||||
* @param str the Java string to be encoded
|
||||
* @param charset the charset used to {@linkplain Charset#newEncoder() encode} the
|
||||
* string bytes
|
||||
* @param srcIndex the starting index of the source string
|
||||
* @param numChars the number of characters to be copied
|
||||
* @return a new native segment containing the encoded string
|
||||
* @throws IndexOutOfBoundsException if either {@code srcIndex} or {@code numChars} are {@code < 0}
|
||||
* @throws IndexOutOfBoundsException if {@code srcIndex > str.length() - numChars}
|
||||
*
|
||||
* @implSpec The default implementation for this method copies the contents of the
|
||||
* provided Java string into a new memory segment obtained by calling
|
||||
* {@code this.allocate(B)}, where {@code B} is the size, in bytes, of
|
||||
* the string encoded using the provided charset
|
||||
* (e.g. {@code str.getBytes(charset).length});
|
||||
* @since 27
|
||||
*/
|
||||
@ForceInline
|
||||
default MemorySegment allocateFrom(String str, Charset charset, int srcIndex, int numChars) {
|
||||
Objects.requireNonNull(charset);
|
||||
Objects.requireNonNull(str);
|
||||
Objects.checkFromIndexSize(srcIndex, numChars, str.length());
|
||||
MemorySegment segment;
|
||||
if (StringSupport.bytesCompatible(str, charset, srcIndex, numChars)) {
|
||||
segment = allocateNoInit(numChars);
|
||||
StringSupport.copyToSegmentRaw(str, segment, 0, srcIndex, numChars);
|
||||
} else {
|
||||
byte[] bytes = str.substring(srcIndex, srcIndex + numChars).getBytes(charset);
|
||||
segment = allocateNoInit(bytes.length);
|
||||
MemorySegment.copy(bytes, 0, segment, ValueLayout.JAVA_BYTE, 0, bytes.length);
|
||||
}
|
||||
return segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new memory segment initialized with the provided byte value}
|
||||
* <p>
|
||||
|
||||
@ -634,10 +634,10 @@ public interface JavaLangAccess {
|
||||
/**
|
||||
* Copy the string bytes to an existing segment, avoiding intermediate copies.
|
||||
*/
|
||||
void copyToSegmentRaw(String string, MemorySegment segment, long offset);
|
||||
void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength);
|
||||
|
||||
/**
|
||||
* Are the string bytes compatible with the given charset?
|
||||
*/
|
||||
boolean bytesCompatible(String string, Charset charset);
|
||||
boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars);
|
||||
}
|
||||
|
||||
@ -832,10 +832,8 @@ public abstract sealed class AbstractInstruction
|
||||
final int slot;
|
||||
final int constant;
|
||||
|
||||
public UnboundIncrementInstruction(int slot, int constant) {
|
||||
super(BytecodeHelpers.validateAndIsWideIinc(slot, constant)
|
||||
? Opcode.IINC_W
|
||||
: Opcode.IINC);
|
||||
public UnboundIncrementInstruction(Opcode op, int slot, int constant) {
|
||||
super(op);
|
||||
this.slot = slot;
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
@ -450,6 +450,13 @@ public class BytecodeHelpers {
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void validateIncrement(Opcode opcode, int slot, int constant) {
|
||||
if (validateAndIsWideIinc(slot, constant) && opcode != Opcode.IINC_W) {
|
||||
throw new IllegalArgumentException(
|
||||
"IINC: operands require wide encoding for %s".formatted(opcode));
|
||||
}
|
||||
}
|
||||
|
||||
public static void validateRet(Opcode opcode, int slot) {
|
||||
if (opcode == Opcode.RET && (slot & ~0xFF) == 0 ||
|
||||
opcode == Opcode.RET_W && (slot & ~0xFFFF) == 0)
|
||||
|
||||
@ -551,6 +551,13 @@ public abstract sealed class AbstractMemorySegmentImpl
|
||||
unsafeGetOffset() == that.unsafeGetOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(long offset, Charset charset, long byteLength) {
|
||||
Utils.checkNonNegativeArgument(byteLength, "byteLength");
|
||||
Objects.requireNonNull(charset);
|
||||
return StringSupport.read(this, offset, charset, byteLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
@ -702,6 +709,16 @@ public abstract sealed class AbstractMemorySegmentImpl
|
||||
}
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
public static long copy(String src, Charset dstEncoding, int srcIndex, MemorySegment dst, long dstOffset, int numChars) {
|
||||
Objects.requireNonNull(src);
|
||||
Objects.requireNonNull(dstEncoding);
|
||||
Objects.requireNonNull(dst);
|
||||
|
||||
AbstractMemorySegmentImpl destImpl = (AbstractMemorySegmentImpl)dst;
|
||||
return StringSupport.copyBytes(src, destImpl, dstEncoding, dstOffset, srcIndex, numChars);
|
||||
}
|
||||
|
||||
// accessors
|
||||
|
||||
@ForceInline
|
||||
|
||||
@ -30,11 +30,14 @@ import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.misc.ScopedMemoryAccess;
|
||||
import jdk.internal.util.Architecture;
|
||||
import jdk.internal.util.ArraysSupport;
|
||||
import jdk.internal.util.Preconditions;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
|
||||
import java.lang.foreign.MemorySegment;
|
||||
import java.lang.reflect.Array;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.lang.foreign.ValueLayout.*;
|
||||
|
||||
@ -58,6 +61,27 @@ public final class StringSupport {
|
||||
};
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
public static String read(AbstractMemorySegmentImpl segment, long offset, Charset charset, long length) {
|
||||
return readBytes(segment, offset, charset, length);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
public static String readBytes(AbstractMemorySegmentImpl segment, long offset, Charset charset, long length) {
|
||||
if (length > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Required length exceeds implementation limit");
|
||||
}
|
||||
final int lengthBytes = (int) length;
|
||||
final byte[] bytes = new byte[lengthBytes];
|
||||
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, lengthBytes);
|
||||
try {
|
||||
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
|
||||
} catch (CharacterCodingException _) {
|
||||
// use replacement characters for malformed input
|
||||
return new String(bytes, charset);
|
||||
}
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
public static void write(AbstractMemorySegmentImpl segment, long offset, Charset charset, String string) {
|
||||
switch (CharsetKind.of(charset)) {
|
||||
@ -70,14 +94,7 @@ public final class StringSupport {
|
||||
@ForceInline
|
||||
private static String readByte(AbstractMemorySegmentImpl segment, long offset, Charset charset) {
|
||||
final int len = strlenByte(segment, offset, segment.byteSize());
|
||||
final byte[] bytes = new byte[len];
|
||||
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len);
|
||||
try {
|
||||
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
|
||||
} catch (CharacterCodingException _) {
|
||||
// use replacement characters for malformed input
|
||||
return new String(bytes, charset);
|
||||
}
|
||||
return readBytes(segment, offset, charset, len);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@ -89,14 +106,7 @@ public final class StringSupport {
|
||||
@ForceInline
|
||||
private static String readShort(AbstractMemorySegmentImpl segment, long offset, Charset charset) {
|
||||
int len = strlenShort(segment, offset, segment.byteSize());
|
||||
byte[] bytes = new byte[len];
|
||||
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len);
|
||||
try {
|
||||
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
|
||||
} catch (CharacterCodingException _) {
|
||||
// use replacement characters for malformed input
|
||||
return new String(bytes, charset);
|
||||
}
|
||||
return readBytes(segment, offset, charset, len);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@ -108,14 +118,7 @@ public final class StringSupport {
|
||||
@ForceInline
|
||||
private static String readInt(AbstractMemorySegmentImpl segment, long offset, Charset charset) {
|
||||
int len = strlenInt(segment, offset, segment.byteSize());
|
||||
byte[] bytes = new byte[len];
|
||||
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len);
|
||||
try {
|
||||
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
|
||||
} catch (CharacterCodingException _) {
|
||||
// use replacement characters for malformed input
|
||||
return new String(bytes, charset);
|
||||
}
|
||||
return readBytes(segment, offset, charset, len);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@ -345,22 +348,26 @@ public final class StringSupport {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean bytesCompatible(String string, Charset charset) {
|
||||
return JAVA_LANG_ACCESS.bytesCompatible(string, charset);
|
||||
public static boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars) {
|
||||
return JAVA_LANG_ACCESS.bytesCompatible(string, charset, srcIndex, numChars);
|
||||
}
|
||||
|
||||
public static int copyBytes(String string, MemorySegment segment, Charset charset, long offset) {
|
||||
if (bytesCompatible(string, charset)) {
|
||||
copyToSegmentRaw(string, segment, offset);
|
||||
return string.length();
|
||||
return copyBytes(string, segment, charset, offset, 0, string.length());
|
||||
}
|
||||
|
||||
public static int copyBytes(String string, MemorySegment segment, Charset charset, long offset, int srcIndex, int numChars) {
|
||||
if (bytesCompatible(string, charset, srcIndex, numChars)) {
|
||||
copyToSegmentRaw(string, segment, offset, srcIndex, numChars);
|
||||
return numChars;
|
||||
} else {
|
||||
byte[] bytes = string.getBytes(charset);
|
||||
byte[] bytes = string.substring(srcIndex, srcIndex + numChars).getBytes(charset);
|
||||
MemorySegment.copy(bytes, 0, segment, JAVA_BYTE, offset, bytes.length);
|
||||
return bytes.length;
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyToSegmentRaw(String string, MemorySegment segment, long offset) {
|
||||
JAVA_LANG_ACCESS.copyToSegmentRaw(string, segment, offset);
|
||||
public static void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength) {
|
||||
JAVA_LANG_ACCESS.copyToSegmentRaw(string, segment, offset, srcIndex, srcLength);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2026, 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
|
||||
@ -120,6 +120,15 @@ public interface TypeMirror extends AnnotatedConstruct {
|
||||
* The results of {@code t1.equals(t2)} and
|
||||
* {@code Types.isSameType(t1, t2)} may differ.
|
||||
*
|
||||
* @apiNote The identity of a {@code TypeMirror} involves implicit
|
||||
* state not directly accessible from its methods, including state
|
||||
* about the presence of unrelated types. {@code TypeMirror}
|
||||
* objects created by different implementations of these
|
||||
* interfaces should <i>not</i> be expected to compare as equal
|
||||
* even if "the same" type is being modeled; this is
|
||||
* analogous to the inequality of {@code Class} objects for the
|
||||
* same class file loaded through different class loaders.
|
||||
*
|
||||
* @param obj the object to be compared with this type
|
||||
* @return {@code true} if the specified object is equal to this one
|
||||
*/
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2026, 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
|
||||
@ -106,6 +106,15 @@ public interface Types {
|
||||
* {@code TypeMirror} objects can have different annotations and
|
||||
* still be considered the same.
|
||||
*
|
||||
* @apiNote The identity of a {@code TypeMirror} involves implicit
|
||||
* state not directly accessible from its methods, including state
|
||||
* about the presence of unrelated types. {@code TypeMirror}
|
||||
* objects created by different implementations of these
|
||||
* interfaces should <i>not</i> be expected to compare as equal
|
||||
* even if "the same" type is being modeled; this is
|
||||
* analogous to the inequality of {@code Class} objects for the
|
||||
* same class file loaded through different class loaders.
|
||||
*
|
||||
* @param t1 the first type
|
||||
* @param t2 the second type
|
||||
* @return {@code true} if and only if the two types are the same
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2026, 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
|
||||
@ -1405,7 +1405,7 @@ public class BasicSplitPaneUI extends SplitPaneUI
|
||||
// If the splitpane has a zero size then no op out of here.
|
||||
// If we execute this function now, we're going to cause ourselves
|
||||
// much grief.
|
||||
if (containerSize.height <= 0 || containerSize.width <= 0 ) {
|
||||
if (containerSize.height <= 0 && containerSize.width <= 0 ) {
|
||||
lastSplitPaneSize = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 2026, 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
|
||||
@ -1403,6 +1403,7 @@ public class TypeAnnotations {
|
||||
break;
|
||||
}
|
||||
}
|
||||
scan(tree.dims);
|
||||
scan(tree.elems);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2026, 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
|
||||
@ -1107,6 +1107,7 @@ public class Annotate {
|
||||
for (List<JCAnnotation> dimAnnos : tree.dimAnnotations)
|
||||
enterTypeAnnotations(dimAnnos, env, sym, false);
|
||||
scan(tree.elemtype);
|
||||
scan(tree.dims);
|
||||
scan(tree.elems);
|
||||
}
|
||||
|
||||
|
||||
@ -47,6 +47,7 @@ import jdk.jpackage.internal.model.ApplicationLayout;
|
||||
import jdk.jpackage.internal.model.Launcher;
|
||||
import jdk.jpackage.internal.model.MacApplication;
|
||||
import jdk.jpackage.internal.model.RuntimeLayout;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
import jdk.jpackage.internal.util.PathUtils;
|
||||
import jdk.jpackage.internal.util.Result;
|
||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -30,8 +30,8 @@ import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT;
|
||||
import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib;
|
||||
import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir;
|
||||
import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.ICON;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.APPCLASS;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.ICON;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_CATEGORY;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_IMAGE_SIGN_IDENTITY;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_STORE;
|
||||
@ -52,11 +52,13 @@ import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG;
|
||||
import static jdk.jpackage.internal.util.function.ExceptionBox.toUnchecked;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo;
|
||||
import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException;
|
||||
import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector;
|
||||
import jdk.jpackage.internal.cli.OptionValue;
|
||||
import jdk.jpackage.internal.cli.Options;
|
||||
import jdk.jpackage.internal.cli.StandardFaOption;
|
||||
import jdk.jpackage.internal.model.ApplicationLaunchers;
|
||||
@ -71,6 +73,7 @@ import jdk.jpackage.internal.model.MacPackage;
|
||||
import jdk.jpackage.internal.model.MacPkgPackage;
|
||||
import jdk.jpackage.internal.model.PackageType;
|
||||
import jdk.jpackage.internal.model.RuntimeLayout;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
import jdk.jpackage.internal.util.Result;
|
||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||
|
||||
@ -276,16 +279,12 @@ final class MacFromOptions {
|
||||
|
||||
final var builder = new MacPackageBuilder(createPackageBuilder(options, app.app(), type));
|
||||
|
||||
app.externalApp()
|
||||
.map(ExternalApplication::extra)
|
||||
.flatMap(MAC_SIGN::findIn)
|
||||
.ifPresent(builder::predefinedAppImageSigned);
|
||||
|
||||
PREDEFINED_RUNTIME_IMAGE.findIn(options)
|
||||
.map(MacBundle::new)
|
||||
.filter(MacBundle::isValid)
|
||||
.map(MacBundle::isSigned)
|
||||
.ifPresent(builder::predefinedAppImageSigned);
|
||||
for (OptionValue<Path> ov : List.of(PREDEFINED_APP_IMAGE, PREDEFINED_RUNTIME_IMAGE)) {
|
||||
ov.findIn(options)
|
||||
.flatMap(MacBundle::fromPath)
|
||||
.map(MacPackagingPipeline::isSigned)
|
||||
.ifPresent(builder::predefinedAppImageSigned);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -76,6 +76,8 @@ import jdk.jpackage.internal.model.MacPackage;
|
||||
import jdk.jpackage.internal.model.Package;
|
||||
import jdk.jpackage.internal.model.PackageType;
|
||||
import jdk.jpackage.internal.util.FileUtils;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
import jdk.jpackage.internal.util.PListReader;
|
||||
import jdk.jpackage.internal.util.PathUtils;
|
||||
import jdk.jpackage.internal.util.function.ThrowingConsumer;
|
||||
|
||||
@ -178,13 +180,10 @@ final class MacPackagingPipeline {
|
||||
builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB)
|
||||
.appImageAction(MacPackagingPipeline::copyJliLib).add();
|
||||
|
||||
final var predefinedRuntimeBundle = Optional.of(
|
||||
new MacBundle(p.predefinedAppImage().orElseThrow())).filter(MacBundle::isValid);
|
||||
|
||||
// Don't create ".package" file.
|
||||
disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE);
|
||||
|
||||
if (predefinedRuntimeBundle.isPresent()) {
|
||||
if (MacBundle.fromPath(p.predefinedAppImage().orElseThrow()).isPresent()) {
|
||||
// The input runtime image is a macOS bundle.
|
||||
// Disable all alterations of the input bundle, but keep the signing enabled.
|
||||
disabledTasks.addAll(List.of(MacCopyAppImageTaskID.values()));
|
||||
@ -195,7 +194,7 @@ final class MacPackagingPipeline {
|
||||
.appImageAction(MacPackagingPipeline::writeRuntimeInfoPlist).add();
|
||||
}
|
||||
|
||||
if (predefinedRuntimeBundle.map(MacBundle::isSigned).orElse(false) && !((MacPackage)p).app().sign()) {
|
||||
if (((MacPackage)p).predefinedAppImageSigned().orElse(false) && !((MacPackage)p).app().sign()) {
|
||||
// The input runtime is a signed bundle; explicit signing is not requested for the package.
|
||||
// Disable the signing, i.e. don't re-sign the input bundle.
|
||||
disabledTasks.add(MacCopyAppImageTaskID.COPY_SIGN);
|
||||
@ -279,6 +278,30 @@ final class MacPackagingPipeline {
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isSigned(MacBundle bundle) {
|
||||
|
||||
var result = toSupplier(Executor.of(
|
||||
"/usr/sbin/spctl",
|
||||
"-vv",
|
||||
"--raw",
|
||||
"--assess",
|
||||
"--type", "exec",
|
||||
bundle.root().toString()).setQuiet(true).saveOutput(true).binaryOutput()::execute).get();
|
||||
|
||||
switch (result.getExitCode()) {
|
||||
case 0, 3 -> {
|
||||
// These exit codes are accompanied with valid plist xml.
|
||||
return toSupplier(() -> {
|
||||
return new PListReader(result.byteStdout()).findValue("assessment:originator").isPresent();
|
||||
}).get();
|
||||
}
|
||||
default -> {
|
||||
// Likely to be an "a sealed resource is missing or invalid" error.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyAppImage(MacPackage pkg, AppImageLayout srcAppImage,
|
||||
AppImageLayout dstAppImage) throws IOException {
|
||||
|
||||
@ -286,7 +309,7 @@ final class MacPackagingPipeline {
|
||||
|
||||
final Optional<MacBundle> srcMacBundle;
|
||||
if (pkg.isRuntimeInstaller()) {
|
||||
srcMacBundle = MacBundle.fromAppImageLayout(srcAppImage);
|
||||
srcMacBundle = macBundleFromAppImageLayout(srcAppImage);
|
||||
} else {
|
||||
srcMacBundle = Optional.empty();
|
||||
}
|
||||
@ -297,7 +320,7 @@ final class MacPackagingPipeline {
|
||||
try {
|
||||
FileUtils.copyRecursive(
|
||||
inputBundle.root(),
|
||||
MacBundle.fromAppImageLayout(dstAppImage).orElseThrow().root(),
|
||||
macBundleFromAppImageLayout(dstAppImage).orElseThrow().root(),
|
||||
LinkOption.NOFOLLOW_LINKS);
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
@ -415,7 +438,7 @@ final class MacPackagingPipeline {
|
||||
|
||||
final var app = env.app();
|
||||
|
||||
final var infoPlistFile = MacBundle.fromAppImageLayout(env.resolvedLayout()).orElseThrow().infoPlistFile();
|
||||
final var infoPlistFile = macBundleFromAppImageLayout(env.resolvedLayout()).orElseThrow().infoPlistFile();
|
||||
|
||||
Log.verbose(I18N.format("message.preparing-info-plist", PathUtils.normalizedAbsolutePathString(infoPlistFile)));
|
||||
|
||||
@ -468,7 +491,7 @@ final class MacPackagingPipeline {
|
||||
}
|
||||
|
||||
final Runnable signAction = () -> {
|
||||
AppImageSigner.createSigner(app, codesignConfigBuilder.create()).accept(MacBundle.fromAppImageLayout(env.resolvedLayout()).orElseThrow());
|
||||
AppImageSigner.createSigner(app, codesignConfigBuilder.create()).accept(macBundleFromAppImageLayout(env.resolvedLayout()).orElseThrow());
|
||||
};
|
||||
|
||||
app.signingConfig().flatMap(AppImageSigningConfig::keychain).map(Keychain::new).ifPresentOrElse(keychain -> {
|
||||
@ -550,7 +573,7 @@ final class MacPackagingPipeline {
|
||||
|
||||
private static MacBundle runtimeBundle(AppImageBuildEnv<MacApplication, AppImageLayout> env) {
|
||||
if (env.app().isRuntime()) {
|
||||
return MacBundle.fromAppImageLayout(env.resolvedLayout()).orElseThrow();
|
||||
return macBundleFromAppImageLayout(env.resolvedLayout()).orElseThrow();
|
||||
} else {
|
||||
return new MacBundle(((MacApplicationLayout)env.resolvedLayout()).runtimeRootDirectory());
|
||||
}
|
||||
@ -595,6 +618,22 @@ final class MacPackagingPipeline {
|
||||
};
|
||||
}
|
||||
|
||||
private static Optional<MacBundle> macBundleFromAppImageLayout(AppImageLayout layout) {
|
||||
final var root = layout.rootDirectory();
|
||||
final var bundleSubdir = root.relativize(layout.runtimeDirectory());
|
||||
final var contentsDirname = Path.of("Contents");
|
||||
var bundleRoot = root;
|
||||
for (int i = 0; i != bundleSubdir.getNameCount(); i++) {
|
||||
var nameComponent = bundleSubdir.getName(i);
|
||||
if (contentsDirname.equals(nameComponent)) {
|
||||
return Optional.of(new MacBundle(bundleRoot));
|
||||
} else {
|
||||
bundleRoot = bundleRoot.resolve(nameComponent);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
private record TaskContextProxy(TaskContext delegate, boolean forApp, boolean copyAppImage) implements TaskContext {
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -28,7 +28,6 @@ import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.toUnmodifiableMap;
|
||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_APP_STORE;
|
||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_MAIN_CLASS;
|
||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_SIGNED;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
@ -96,9 +95,6 @@ public interface MacApplication extends Application, MacApplicationMixin {
|
||||
}
|
||||
|
||||
public enum ExtraAppImageFileField {
|
||||
SIGNED(MAC_SIGNED, app -> {
|
||||
return Optional.of(Boolean.toString(app.sign()));
|
||||
}),
|
||||
APP_STORE(MAC_APP_STORE, app -> {
|
||||
return Optional.of(Boolean.toString(app.appStore()));
|
||||
}),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -73,6 +73,7 @@ final class OptionSpecBuilder<T> {
|
||||
valuePattern = other.valuePattern;
|
||||
converterBuilder = other.converterBuilder.copy();
|
||||
validatorBuilder = other.validatorBuilder.copy();
|
||||
validator = other.validator;
|
||||
|
||||
if (other.arrayDefaultValue != null) {
|
||||
arrayDefaultValue = Arrays.copyOf(other.arrayDefaultValue, other.arrayDefaultValue.length);
|
||||
@ -135,10 +136,20 @@ final class OptionSpecBuilder<T> {
|
||||
scope,
|
||||
OptionSpecBuilder.this.mergePolicy().orElse(MergePolicy.CONCATENATE),
|
||||
defaultArrayOptionalValue(),
|
||||
Optional.of(arryValuePattern()),
|
||||
Optional.of(arrayValuePattern()),
|
||||
OptionSpecBuilder.this.description().orElse(""));
|
||||
}
|
||||
|
||||
Optional<? extends Validator<T, RuntimeException>> createValidator() {
|
||||
return Optional.ofNullable(validator).or(() -> {
|
||||
if (validatorBuilder.hasValidatingMethod()) {
|
||||
return Optional.of(validatorBuilder.create());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
OptionSpecBuilder<T> tokenizer(String splitRegexp) {
|
||||
Objects.requireNonNull(splitRegexp);
|
||||
return tokenizer(str -> {
|
||||
@ -162,11 +173,13 @@ final class OptionSpecBuilder<T> {
|
||||
|
||||
OptionSpecBuilder<T> validatorExceptionFormatString(String v) {
|
||||
validatorBuilder.formatString(v);
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
OptionSpecBuilder<T> validatorExceptionFormatString(UnaryOperator<String> mutator) {
|
||||
validatorBuilder.formatString(mutator.apply(validatorBuilder.formatString().orElse(null)));
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -182,6 +195,7 @@ final class OptionSpecBuilder<T> {
|
||||
|
||||
OptionSpecBuilder<T> validatorExceptionFactory(OptionValueExceptionFactory<? extends RuntimeException> v) {
|
||||
validatorBuilder.exceptionFactory(v);
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -225,18 +239,27 @@ final class OptionSpecBuilder<T> {
|
||||
|
||||
OptionSpecBuilder<T> validator(Predicate<T> v) {
|
||||
validatorBuilder.predicate(v::test);
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("overloads")
|
||||
OptionSpecBuilder<T> validator(Consumer<T> v) {
|
||||
validatorBuilder.consumer(v::accept);
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("overloads")
|
||||
OptionSpecBuilder<T> validator(UnaryOperator<Validator.Builder<T, RuntimeException>> mutator) {
|
||||
validatorBuilder = mutator.apply(validatorBuilder);
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
OptionSpecBuilder<T> validator(Validator<T, RuntimeException> v) {
|
||||
validatorBuilder.predicate(null).consumer(null);
|
||||
validator = Objects.requireNonNull(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -247,6 +270,7 @@ final class OptionSpecBuilder<T> {
|
||||
|
||||
OptionSpecBuilder<T> withoutValidator() {
|
||||
validatorBuilder.predicate(null).consumer(null);
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -423,14 +447,6 @@ final class OptionSpecBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Validator<T, ? extends RuntimeException>> createValidator() {
|
||||
if (validatorBuilder.hasValidatingMethod()) {
|
||||
return Optional.of(validatorBuilder.create());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private OptionValueConverter<T[]> createArrayConverter() {
|
||||
final var newBuilder = converterBuilder.copy();
|
||||
newBuilder.tokenizer(Optional.ofNullable(arrayTokenizer).orElse(str -> {
|
||||
@ -440,7 +456,7 @@ final class OptionSpecBuilder<T> {
|
||||
return newBuilder.createArray();
|
||||
}
|
||||
|
||||
private String arryValuePattern() {
|
||||
private String arrayValuePattern() {
|
||||
final var elementValuePattern = OptionSpecBuilder.this.valuePattern().orElseThrow();
|
||||
if (arrayValuePatternSeparator == null) {
|
||||
return elementValuePattern;
|
||||
@ -468,6 +484,7 @@ final class OptionSpecBuilder<T> {
|
||||
private String valuePattern;
|
||||
private OptionValueConverter.Builder<T> converterBuilder = OptionValueConverter.build();
|
||||
private Validator.Builder<T, RuntimeException> validatorBuilder = Validator.build();
|
||||
private Validator<T, RuntimeException> validator;
|
||||
|
||||
private T[] arrayDefaultValue;
|
||||
private String arrayValuePatternSeparator;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -169,15 +169,6 @@ public final class StandardAppImageFileOption {
|
||||
.mutate(setPlatformScope(OperatingSystem.MACOS))
|
||||
.toOptionValueBuilder().id(StandardOption.MAC_APP_STORE.id()).create();
|
||||
|
||||
/**
|
||||
* Is an application image is signed. macOS-only.
|
||||
*/
|
||||
public static final OptionValue<Boolean> MAC_SIGNED = booleanOption("signed")
|
||||
.inScope(AppImageFileOptionScope.APP)
|
||||
.mutate(setPlatformScope(OperatingSystem.MACOS))
|
||||
.toOptionValueBuilder().id(StandardOption.MAC_SIGN.id()).create();
|
||||
|
||||
|
||||
public static final class InvalidOptionValueException extends RuntimeException {
|
||||
|
||||
InvalidOptionValueException(String str, Throwable t) {
|
||||
|
||||
@ -233,6 +233,12 @@ public final class StandardOption {
|
||||
.mutate(createOptionSpecBuilderMutator((b, context) -> {
|
||||
if (context.os() == OperatingSystem.MACOS) {
|
||||
b.description("help.option.app-image" + resourceKeySuffix(context.os()));
|
||||
var directoryValidator = b.createValidator().orElseThrow();
|
||||
var macBundleValidator = b
|
||||
.validatorExceptionFormatString("error.parameter-not-mac-bundle")
|
||||
.validator(StandardValidator.IS_VALID_MAC_BUNDLE)
|
||||
.createValidator().orElseThrow();
|
||||
b.validator(Validator.and(directoryValidator, macBundleValidator));
|
||||
}
|
||||
}))
|
||||
.create();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -38,6 +38,7 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import jdk.jpackage.internal.cli.Validator.ValidatingConsumerException;
|
||||
import jdk.jpackage.internal.util.FileUtils;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
|
||||
final public class StandardValidator {
|
||||
|
||||
@ -138,6 +139,10 @@ final public class StandardValidator {
|
||||
return true;
|
||||
};
|
||||
|
||||
public static Predicate<Path> IS_VALID_MAC_BUNDLE = path -> {
|
||||
return MacBundle.fromPath(path).isPresent();
|
||||
};
|
||||
|
||||
|
||||
public static final class DirectoryListingIOException extends RuntimeException {
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -24,20 +24,55 @@
|
||||
*/
|
||||
package jdk.jpackage.internal.cli;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@FunctionalInterface
|
||||
interface Validator<T, U extends Exception> {
|
||||
|
||||
List<U> validate(OptionName optionName, ParsedValue<T> optionValue);
|
||||
|
||||
default Validator<T, ? extends Exception> andThen(Validator<T, ? extends Exception> after) {
|
||||
return reduce(this, after);
|
||||
default Validator<T, ? extends Exception> and(Validator<T, ? extends Exception> after) {
|
||||
Objects.requireNonNull(after);
|
||||
var before = this;
|
||||
return (optionName, optionValue) -> {
|
||||
return Stream.concat(
|
||||
before.validate(optionName, optionValue).stream(),
|
||||
after.validate(optionName, optionValue).stream()
|
||||
).toList();
|
||||
};
|
||||
}
|
||||
|
||||
default Validator<T, ? extends Exception> or(Validator<T, ? extends Exception> after) {
|
||||
Objects.requireNonNull(after);
|
||||
var before = this;
|
||||
return (optionName, optionValue) -> {
|
||||
var bErrors = before.validate(optionName, optionValue);
|
||||
if (bErrors.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
var aErrors = after.validate(optionName, optionValue);
|
||||
if (aErrors.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return Stream.concat(bErrors.stream(), aErrors.stream()).toList();
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T, U extends Exception> Validator<T, U> and(Validator<T, U> first, Validator<T, U> second) {
|
||||
return (Validator<T, U>)first.and(second);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T, U extends Exception> Validator<T, U> or(Validator<T, U> first, Validator<T, U> second) {
|
||||
return (Validator<T, U>)first.or(second);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -251,15 +286,4 @@ interface Validator<T, U extends Exception> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static <T> Validator<T, ? extends Exception> reduce(Validator<T, ? extends Exception>... validators) {
|
||||
@SuppressWarnings("varargs")
|
||||
var theValidators = List.of(validators);
|
||||
return (optionName, optionValue) -> {
|
||||
return theValidators.stream().map(validator -> {
|
||||
return validator.validate(optionName, optionValue);
|
||||
}).flatMap(Collection::stream).map(Exception.class::cast).toList();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +75,7 @@ error.parameter-not-directory=The value "{0}" provided for parameter {1} is not
|
||||
error.parameter-not-empty-directory=The value "{0}" provided for parameter {1} is not an empty directory or non existent path
|
||||
error.parameter-not-url=The value "{0}" provided for parameter {1} is not a valid URL
|
||||
error.parameter-not-launcher-shortcut-dir=The value "{0}" provided for parameter {1} is not a valid shortcut startup directory
|
||||
error.parameter-not-mac-bundle=The value "{0}" provided for parameter {1} is not a valid macOS bundle
|
||||
error.path-parameter-ioexception=I/O error accessing path value "{0}" of parameter {1}
|
||||
error.parameter-add-launcher-malformed=The value "{0}" provided for parameter {1} does not match the pattern <name>=<file path>
|
||||
error.parameter-add-launcher-not-file=The value of path to a property file "{0}" provided for additional launcher "{1}" is not a valid file path
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -23,54 +23,49 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.internal;
|
||||
package jdk.jpackage.internal.util;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import jdk.jpackage.internal.model.AppImageLayout;
|
||||
|
||||
/**
|
||||
* An abstraction of macOS Application bundle.
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles">https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles</a>
|
||||
*/
|
||||
record MacBundle(Path root) {
|
||||
public record MacBundle(Path root) {
|
||||
|
||||
MacBundle {
|
||||
public MacBundle {
|
||||
Objects.requireNonNull(root);
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
public boolean isValid() {
|
||||
return Files.isDirectory(contentsDir()) && Files.isDirectory(macOsDir()) && Files.isRegularFile(infoPlistFile());
|
||||
}
|
||||
|
||||
boolean isSigned() {
|
||||
return Files.isDirectory(contentsDir().resolve("_CodeSignature"));
|
||||
}
|
||||
|
||||
Path contentsDir() {
|
||||
public Path contentsDir() {
|
||||
return root.resolve("Contents");
|
||||
}
|
||||
|
||||
Path homeDir() {
|
||||
public Path homeDir() {
|
||||
return contentsDir().resolve("Home");
|
||||
}
|
||||
|
||||
Path macOsDir() {
|
||||
public Path macOsDir() {
|
||||
return contentsDir().resolve("MacOS");
|
||||
}
|
||||
|
||||
Path resourcesDir() {
|
||||
public Path resourcesDir() {
|
||||
return contentsDir().resolve("Resources");
|
||||
}
|
||||
|
||||
Path infoPlistFile() {
|
||||
public Path infoPlistFile() {
|
||||
return contentsDir().resolve("Info.plist");
|
||||
}
|
||||
|
||||
static Optional<MacBundle> fromPath(Path path) {
|
||||
public static Optional<MacBundle> fromPath(Path path) {
|
||||
var bundle = new MacBundle(path);
|
||||
if (bundle.isValid()) {
|
||||
return Optional.of(bundle);
|
||||
@ -78,20 +73,4 @@ record MacBundle(Path root) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
static Optional<MacBundle> fromAppImageLayout(AppImageLayout layout) {
|
||||
final var root = layout.rootDirectory();
|
||||
final var bundleSubdir = root.relativize(layout.runtimeDirectory());
|
||||
final var contentsDirname = Path.of("Contents");
|
||||
var bundleRoot = root;
|
||||
for (int i = 0; i != bundleSubdir.getNameCount(); i++) {
|
||||
var nameComponent = bundleSubdir.getName(i);
|
||||
if (contentsDirname.equals(nameComponent)) {
|
||||
return Optional.of(new MacBundle(bundleRoot));
|
||||
} else {
|
||||
bundleRoot = bundleRoot.resolve(nameComponent);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@ -29,13 +29,14 @@ import com.sun.javatest.TestResult;
|
||||
import com.sun.javatest.regtest.config.RegressionParameters;
|
||||
import jdk.test.failurehandler.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The jtreg test execution observer, which gathers info about
|
||||
@ -85,11 +86,15 @@ public class GatherDiagnosticInfoObserver implements Harness.Observer {
|
||||
testJdk, compileJdk);
|
||||
gatherEnvInfo(workDir, name, log,
|
||||
gathererFactory.getEnvironmentInfoGatherer());
|
||||
Files.walk(workDir)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(f -> (f.getFileName().toString().contains("core") || f.getFileName().toString().contains("mdmp")))
|
||||
.forEach(core -> gatherCoreInfo(workDir, name,
|
||||
core, log, gathererFactory.getCoreInfoGatherer()));
|
||||
// generate a cores.html file after parsing the core dump files (if any)
|
||||
List<Path> coreFiles;
|
||||
try (Stream<Path> paths = Files.walk(workDir)) {
|
||||
coreFiles = paths.filter(Files::isRegularFile)
|
||||
.filter(f -> (f.getFileName().toString().contains("core")
|
||||
|| f.getFileName().toString().contains("mdmp")))
|
||||
.toList();
|
||||
}
|
||||
gatherCoreInfo(workDir, name, coreFiles, log, gathererFactory.getCoreInfoGatherer());
|
||||
} catch (Throwable e) {
|
||||
log.printf("ERROR: exception in observer %s:", name);
|
||||
e.printStackTrace(log);
|
||||
@ -103,16 +108,22 @@ public class GatherDiagnosticInfoObserver implements Harness.Observer {
|
||||
}
|
||||
}
|
||||
|
||||
private void gatherCoreInfo(Path workDir, String name, Path core, PrintWriter log,
|
||||
CoreInfoGatherer gatherer) {
|
||||
private void gatherCoreInfo(Path workDir, String name, List<Path> coreFiles,
|
||||
PrintWriter log, CoreInfoGatherer gatherer) {
|
||||
if (coreFiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try (HtmlPage html = new HtmlPage(workDir, CORES_OUTPUT, true)) {
|
||||
try (ElapsedTimePrinter timePrinter
|
||||
= new ElapsedTimePrinter(new Stopwatch(), name, log)) {
|
||||
gatherer.gatherCoreInfo(html.getRootSection(), core);
|
||||
// gather information from the contents of each core file
|
||||
for (Path coreFile : coreFiles) {
|
||||
gatherer.gatherCoreInfo(html.getRootSection(), coreFile);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.printf("ERROR: exception in observer on getting environment "
|
||||
+ "information %s:", name);
|
||||
log.printf("ERROR: exception in %s observer while gathering information from"
|
||||
+ " core dump file", name);
|
||||
e.printStackTrace(log);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -202,7 +202,7 @@ static void test_canonicalize_constraints_random() {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(opto, canonicalize_constraints) {
|
||||
TEST_VM(opto, canonicalize_constraints) {
|
||||
test_canonicalize_constraints_trivial();
|
||||
test_canonicalize_constraints_exhaustive<intn_t<1>, uintn_t<1>>();
|
||||
test_canonicalize_constraints_exhaustive<intn_t<2>, uintn_t<2>>();
|
||||
|
||||
@ -162,6 +162,35 @@ TEST_VM(AtomicIntegerTest, cmpxchg_int64) {
|
||||
Support().test();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct AtomicIntegerCmpsetTestSupport {
|
||||
Atomic<T> _test_value;
|
||||
|
||||
AtomicIntegerCmpsetTestSupport() : _test_value{} {}
|
||||
|
||||
void test() {
|
||||
T zero = 0;
|
||||
T five = 5;
|
||||
T ten = 10;
|
||||
_test_value.store_relaxed(zero);
|
||||
EXPECT_FALSE(_test_value.compare_set(five, ten));
|
||||
EXPECT_EQ(zero, _test_value.load_relaxed());
|
||||
EXPECT_TRUE(_test_value.compare_set(zero, ten));
|
||||
EXPECT_EQ(ten, _test_value.load_relaxed());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_VM(AtomicIntegerTest, cmpset_int32) {
|
||||
using Support = AtomicIntegerCmpsetTestSupport<int32_t>;
|
||||
Support().test();
|
||||
}
|
||||
|
||||
TEST_VM(AtomicIntegerTest, cmpset_int64) {
|
||||
// Check if 64-bit atomics are available on the machine.
|
||||
using Support = AtomicIntegerCmpsetTestSupport<int64_t>;
|
||||
Support().test();
|
||||
}
|
||||
|
||||
struct AtomicXchgAndCmpxchg1ByteStressSupport {
|
||||
char _default_val;
|
||||
int _base;
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 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.
|
||||
*/
|
||||
|
||||
package compiler.c2.igvn;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8372302
|
||||
* @summary ModINode::Ideal and ModLNode::Ideal use an intermediate "hook" node
|
||||
* to keep stuff alive between phase->transform(...) calls. In some cases,
|
||||
* this node is not properly deleted before returning, causing failure
|
||||
* in the verification because the node count has changed. This test
|
||||
* ensures that the intermediate node gets destroyed before returning.
|
||||
* @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
|
||||
* -Xcomp -XX:-TieredCompilation
|
||||
* -XX:CompileCommand=compileonly,${test.main.class}::test*
|
||||
* -XX:VerifyIterativeGVN=1110
|
||||
* ${test.main.class}
|
||||
* @run main ${test.main.class}
|
||||
*
|
||||
*/
|
||||
|
||||
public class TestModIdealCreatesUselessNode {
|
||||
static int test0(int x) {
|
||||
return x % Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
static long test1(long x) {
|
||||
return x % Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
test0(0);
|
||||
test1(0L);
|
||||
}
|
||||
}
|
||||
@ -274,6 +274,7 @@ public final class Operations {
|
||||
ops.add(Expression.make(BOOLEANS, "Boolean.logicalXor(", BOOLEANS, ", ", BOOLEANS, ")"));
|
||||
|
||||
// TODO: Math and other classes.
|
||||
// Note: Math.copySign is non-deterministic because of NaN having encoding with sign bit set and unset.
|
||||
|
||||
// Make sure the list is not modifiable.
|
||||
return List.copyOf(ops);
|
||||
@ -294,7 +295,8 @@ public final class Operations {
|
||||
ops.add(Expression.make(INTS, "Float16.compare(", FLOAT16, ",", FLOAT16, ")"));
|
||||
addComparisonOperations(ops, "Float16.compare", FLOAT16);
|
||||
ops.add(Expression.make(INTS, "(", FLOAT16, ").compareTo(", FLOAT16, ")"));
|
||||
ops.add(Expression.make(FLOAT16, "Float16.copySign(", FLOAT16, ",", FLOAT16, ")"));
|
||||
// Note: There are NaN encodings with bit set or unset.
|
||||
ops.add(Expression.make(FLOAT16, "Float16.copySign(", FLOAT16, ",", FLOAT16, ")", WITH_NONDETERMINISTIC_RESULT));
|
||||
ops.add(Expression.make(FLOAT16, "Float16.divide(", FLOAT16, ",", FLOAT16, ")"));
|
||||
ops.add(Expression.make(BOOLEANS, "", FLOAT16, ".equals(", FLOAT16, ")"));
|
||||
// Note: there are multiple NaN values with different bit representations.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018 by SAP AG, Walldorf, Germany.
|
||||
* Copyright (c) 2018, 2026 SAP SE. 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
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018 by SAP AG, Walldorf, Germany.
|
||||
* Copyright (c) 2018, 2026 SAP SE. 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
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011 SAP AG. All Rights Reserved.
|
||||
* Copyright (c) 2011, 2026 SAP SE. 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
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011 SAP AG. All Rights Reserved.
|
||||
* Copyright (c) 2011, 2026 SAP SE. 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
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011 SAP AG. All Rights Reserved.
|
||||
* Copyright (c) 2011, 2026 SAP SE. 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
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011 SAP AG. All Rights Reserved.
|
||||
* Copyright (c) 2011, 2026 SAP SE. 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
|
||||
|
||||
185
test/jdk/build/CheckFiles.java
Normal file
185
test/jdk/build/CheckFiles.java
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright (c) 2026 SAP SE. All rights reserved.
|
||||
* Copyright (c) 2026, 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.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import jdk.test.lib.Platform;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Check for unwanted file (types/extensions) in the jdk image
|
||||
* @library /test/lib
|
||||
* @requires !vm.debug
|
||||
* @run main CheckFiles
|
||||
*/
|
||||
public class CheckFiles {
|
||||
|
||||
// Set this property on command line to scan an alternate dir or file:
|
||||
// JTREG=JAVA_OPTIONS=-Djdk.test.build.CheckFiles.dir=/path/to/dir
|
||||
public static final String DIR_PROPERTY = "jdk.test.build.CheckFiles.dir";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String jdkPathString = System.getProperty("test.jdk");
|
||||
Path jdkHome = Paths.get(jdkPathString);
|
||||
|
||||
Path mainDirToScan = jdkHome;
|
||||
String overrideDir = System.getProperty(DIR_PROPERTY);
|
||||
if (overrideDir != null) {
|
||||
mainDirToScan = Paths.get(overrideDir);
|
||||
}
|
||||
|
||||
System.out.println("Main directory to scan:" + mainDirToScan);
|
||||
Path binDir = mainDirToScan.resolve("bin");
|
||||
Path libDir = mainDirToScan.resolve("lib");
|
||||
Path includeDir = mainDirToScan.resolve("include");
|
||||
Path jmodsDir = mainDirToScan.resolve("jmods");
|
||||
|
||||
System.out.println("Bin directory to scan:" + binDir);
|
||||
ArrayList<String> allowedEndingsBinDir = new ArrayList<>();
|
||||
// UNIX - no extensions are allowed; Windows : .dll, .exe, .pdb, .jsa
|
||||
if (Platform.isWindows()) {
|
||||
allowedEndingsBinDir.add(".dll");
|
||||
allowedEndingsBinDir.add(".exe");
|
||||
allowedEndingsBinDir.add(".pdb");
|
||||
allowedEndingsBinDir.add(".jsa");
|
||||
}
|
||||
boolean binDirRes = scanFiles(binDir, allowedEndingsBinDir);
|
||||
|
||||
System.out.println("Lib directory to scan:" + libDir);
|
||||
ArrayList<String> allowedEndingsLibDir = new ArrayList<>();
|
||||
allowedEndingsLibDir.add(".jfc"); // jfr config files
|
||||
allowedEndingsLibDir.add("cacerts");
|
||||
allowedEndingsLibDir.add("blocked.certs");
|
||||
allowedEndingsLibDir.add("public_suffix_list.dat");
|
||||
allowedEndingsLibDir.add("classlist");
|
||||
allowedEndingsLibDir.add("fontconfig.bfc");
|
||||
allowedEndingsLibDir.add("fontconfig.properties.src");
|
||||
allowedEndingsLibDir.add("ct.sym");
|
||||
allowedEndingsLibDir.add("jrt-fs.jar");
|
||||
allowedEndingsLibDir.add("jvm.cfg");
|
||||
allowedEndingsLibDir.add("modules");
|
||||
allowedEndingsLibDir.add("psfontj2d.properties");
|
||||
allowedEndingsLibDir.add("psfont.properties.ja");
|
||||
allowedEndingsLibDir.add("src.zip");
|
||||
allowedEndingsLibDir.add("tzdb.dat");
|
||||
if (Platform.isWindows()) {
|
||||
allowedEndingsLibDir.add(".lib");
|
||||
allowedEndingsLibDir.add("tzmappings");
|
||||
} else {
|
||||
allowedEndingsLibDir.add("jexec");
|
||||
allowedEndingsLibDir.add("jspawnhelper");
|
||||
allowedEndingsLibDir.add(".jsa");
|
||||
if (Platform.isOSX()) {
|
||||
allowedEndingsLibDir.add("shaders.metallib");
|
||||
allowedEndingsLibDir.add(".dylib");
|
||||
} else {
|
||||
allowedEndingsLibDir.add(".so");
|
||||
}
|
||||
if (Platform.isAix()) {
|
||||
allowedEndingsLibDir.add("tzmappings");
|
||||
}
|
||||
}
|
||||
boolean libDirRes = scanFiles(libDir, allowedEndingsLibDir);
|
||||
|
||||
if (binDirRes) {
|
||||
System.out.println("Bin directory scan successful.");
|
||||
} else {
|
||||
throw new Error("bin dir scan failed");
|
||||
}
|
||||
|
||||
if (libDirRes) {
|
||||
System.out.println("Lib directory scan successful.");
|
||||
} else {
|
||||
throw new Error("lib dir scan failed");
|
||||
}
|
||||
|
||||
if (Files.isDirectory(includeDir)) {
|
||||
System.out.println("Include directory to scan:" + includeDir);
|
||||
ArrayList<String> allowedEndingsIncludeDir = new ArrayList<>();
|
||||
allowedEndingsIncludeDir.add(".h");
|
||||
allowedEndingsIncludeDir.add(".hpp");
|
||||
boolean includeDirRes = scanFiles(includeDir, allowedEndingsIncludeDir);
|
||||
if (includeDirRes) {
|
||||
System.out.println("Include directory scan successful.");
|
||||
} else {
|
||||
throw new Error("include dir scan failed");
|
||||
}
|
||||
}
|
||||
|
||||
// when enabling "JEP 493: Linking Run-Time Images without JMODs" we do not
|
||||
// have the jmods folder at all, so first test the presence of the folder
|
||||
if (Files.isDirectory(jmodsDir)) {
|
||||
System.out.println("Jmods directory to scan:" + jmodsDir);
|
||||
ArrayList<String> allowedEndingsJmodsDir = new ArrayList<>();
|
||||
allowedEndingsJmodsDir.add(".jmod");
|
||||
boolean jmodsDirRes = scanFiles(jmodsDir, allowedEndingsJmodsDir);
|
||||
if (jmodsDirRes) {
|
||||
System.out.println("Jmods directory scan successful.");
|
||||
} else {
|
||||
throw new Error("jmods dir scan failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean scanFiles(Path root, ArrayList<String> allowedEndings) throws IOException {
|
||||
AtomicBoolean badFileFound = new AtomicBoolean(false);
|
||||
|
||||
Files.walkFileTree(root, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
String fullFileName = file.toString();
|
||||
String fileName = file.getFileName().toString();
|
||||
System.out.println(" visiting file:" + fullFileName);
|
||||
checkFile(fileName, allowedEndings);
|
||||
return super.visitFile(file, attrs);
|
||||
}
|
||||
|
||||
private void checkFile(String name, ArrayList<String> allowedEndings) {
|
||||
if (allowedEndings.isEmpty()) { // no file extensions allowed
|
||||
int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
System.out.println(" --> ERROR this file is not allowed:" + name);
|
||||
badFileFound.set(true);
|
||||
}
|
||||
} else {
|
||||
boolean allowed = allowedEndings.stream().anyMatch(name::endsWith);
|
||||
if (! allowed) {
|
||||
System.out.println(" --> ERROR this file is not allowed:" + name);
|
||||
badFileFound.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return !badFileFound.get();
|
||||
}
|
||||
}
|
||||
@ -37,6 +37,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import jdk.internal.foreign.AbstractMemorySegmentImpl;
|
||||
@ -102,6 +103,140 @@ public class TestStringEncoding {
|
||||
}
|
||||
}
|
||||
|
||||
@Test(dataProvider = "strings")
|
||||
public void testStringsLength(String testString) {
|
||||
if (!testString.isEmpty()) {
|
||||
for (Charset charset : Charset.availableCharsets().values()) {
|
||||
if (charset.canEncode()) {
|
||||
for (Arena arena : arenas()) {
|
||||
try (arena) {
|
||||
MemorySegment text = arena.allocateFrom(testString, charset, 0, testString.length());
|
||||
long length = text.byteSize();
|
||||
assertEquals(length, testString.getBytes(charset).length);
|
||||
String roundTrip = text.getString(0, charset, length);
|
||||
if (charset.newEncoder().canEncode(testString)) {
|
||||
assertEquals(roundTrip, testString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(dataProvider = "strings")
|
||||
public void testStringsCopy(String testString) {
|
||||
if (!testString.isEmpty()) {
|
||||
for (Charset charset : Charset.availableCharsets().values()) {
|
||||
if (charset.canEncode()) {
|
||||
for (Arena arena : arenas()) {
|
||||
try (arena) {
|
||||
byte[] bytes = testString.getBytes(charset);
|
||||
MemorySegment text = arena.allocate(JAVA_BYTE, bytes.length);
|
||||
MemorySegment.copy(testString, charset, 0, text, 0, testString.length());
|
||||
String roundTrip = text.getString(0, charset, bytes.length);
|
||||
if (charset.newEncoder().canEncode(testString)) {
|
||||
assertEquals(roundTrip, testString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringsLengthNegative() {
|
||||
try (Arena arena = Arena.ofConfined()) {
|
||||
var segment = arena.allocateFrom("abc");
|
||||
assertThrows(IllegalArgumentException.class, () -> segment.getString(1, StandardCharsets.UTF_8, -1));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyThrows() {
|
||||
try (Arena arena = Arena.ofConfined()) {
|
||||
String testString = "abc";
|
||||
String testString_notBytesCompatible = "snowman \u26C4";
|
||||
MemorySegment text = arena.allocate(JAVA_BYTE, 3);
|
||||
MemorySegment text_notBytesCompatible = arena.allocate(JAVA_BYTE,
|
||||
testString_notBytesCompatible.getBytes(StandardCharsets.UTF_8).length);
|
||||
MemorySegment.copy(testString, StandardCharsets.UTF_8, 0, text, 0, testString.length());
|
||||
MemorySegment.copy(testString_notBytesCompatible, StandardCharsets.UTF_8, 0,
|
||||
text_notBytesCompatible, 0,
|
||||
testString_notBytesCompatible.length());
|
||||
// srcIndex < 0
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
MemorySegment.copy(testString, StandardCharsets.UTF_8, -1, text, 0, testString.length()));
|
||||
// dstOffset < 0
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
MemorySegment.copy(testString, StandardCharsets.UTF_8, 0, text, -1, testString.length()));
|
||||
// numChars < 0
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
MemorySegment.copy(testString, StandardCharsets.UTF_8, 0, text, 0, -1));
|
||||
// srcIndex + numChars > length
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
MemorySegment.copy(testString, StandardCharsets.UTF_8, 1, text, 0, testString.length()));
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
MemorySegment.copy(testString, StandardCharsets.UTF_8, 0, text, 0, testString.length() + 1));
|
||||
// dstOffset > byteSize() - B
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
MemorySegment.copy(testString, StandardCharsets.UTF_8, 0, text, 1, testString.length()));
|
||||
// srcIndex + numChars overflows
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
MemorySegment.copy(testString, StandardCharsets.UTF_8, Integer.MAX_VALUE, text, 0, Integer.MAX_VALUE + 3));
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
MemorySegment.copy(testString_notBytesCompatible, StandardCharsets.UTF_8, Integer.MAX_VALUE, text, 0, Integer.MAX_VALUE + 3));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllocateFromThrows() {
|
||||
try (Arena arena = Arena.ofConfined()) {
|
||||
String testString = "abc";
|
||||
String testString_notBytesCompatible = "snowman \u26C4";
|
||||
arena.allocateFrom(testString, StandardCharsets.UTF_8, 0, testString.length());
|
||||
arena.allocateFrom(testString, StandardCharsets.UTF_8, 2, 1);
|
||||
// srcIndex < 0
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
arena.allocateFrom(testString, StandardCharsets.UTF_8, -1, testString.length()));
|
||||
// numChars < 0
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
arena.allocateFrom(testString, StandardCharsets.UTF_8, 0, -1));
|
||||
// srcIndex + numChars > length
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
arena.allocateFrom(testString, StandardCharsets.UTF_8, 0, testString.length() + 1));
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
arena.allocateFrom(testString, StandardCharsets.UTF_8, 1, testString.length()));
|
||||
// srcIndex + numChars overflows
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
arena.allocateFrom(testString, StandardCharsets.UTF_8, 3, Integer.MAX_VALUE));
|
||||
assertThrows(IndexOutOfBoundsException.class, () -> arena.allocateFrom(
|
||||
testString_notBytesCompatible, StandardCharsets.UTF_8, 3, Integer.MAX_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStringThrows() {
|
||||
try (Arena arena = Arena.ofConfined()) {
|
||||
String testString = "abc";
|
||||
MemorySegment text = arena.allocateFrom(testString, StandardCharsets.UTF_8, 0, testString.length());
|
||||
text.getString(0, StandardCharsets.UTF_8, 3);
|
||||
// unsupported string size
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
text.getString(0, StandardCharsets.UTF_8, Integer.MAX_VALUE + 1L));
|
||||
// offset < 0
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
text.getString(-1, StandardCharsets.UTF_8, 3));
|
||||
// offset > byteSize() - length
|
||||
assertThrows(IndexOutOfBoundsException.class, () ->
|
||||
text.getString(1, StandardCharsets.UTF_8, 3));
|
||||
// length < 0
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
text.getString(0, StandardCharsets.UTF_8, -1));
|
||||
}
|
||||
}
|
||||
|
||||
@Test(dataProvider = "strings")
|
||||
public void testStringsHeap(String testString) {
|
||||
for (Charset charset : singleByteCharsets()) {
|
||||
@ -221,6 +356,74 @@ public class TestStringEncoding {
|
||||
}
|
||||
}
|
||||
|
||||
@Test(dataProvider = "strings")
|
||||
public void testSubstringGetString(String testString) {
|
||||
if (testString.length() < 3 || !containsOnlyRegularCharacters(testString)) {
|
||||
return;
|
||||
}
|
||||
for (var charset : singleByteCharsets()) {
|
||||
for (var arena: arenas()) {
|
||||
try (arena) {
|
||||
MemorySegment text = arena.allocateFrom(testString, charset, 0, testString.length());
|
||||
for (int srcIndex = 0; srcIndex <= testString.length(); srcIndex++) {
|
||||
for (int numChars = 0; numChars <= testString.length() - srcIndex; numChars++) {
|
||||
// this test assumes single-byte charsets
|
||||
String roundTrip = text.getString(srcIndex, charset, numChars);
|
||||
String substring = testString.substring(srcIndex, srcIndex + numChars);
|
||||
assertEquals(roundTrip, substring);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(dataProvider = "strings")
|
||||
public void testSubstringAllocate(String testString) {
|
||||
if (testString.length() < 3 || !containsOnlyRegularCharacters(testString)) {
|
||||
return;
|
||||
}
|
||||
for (var charset : singleByteCharsets()) {
|
||||
for (var arena: arenas()) {
|
||||
try (arena) {
|
||||
for (int srcIndex = 0; srcIndex <= testString.length(); srcIndex++) {
|
||||
for (int numChars = 0; numChars <= testString.length() - srcIndex; numChars++) {
|
||||
MemorySegment text = arena.allocateFrom(testString, charset, srcIndex, numChars);
|
||||
String substring = testString.substring(srcIndex, srcIndex + numChars);
|
||||
assertEquals(text.byteSize(), substring.getBytes(charset).length);
|
||||
String roundTrip = text.getString(0, charset, text.byteSize());
|
||||
assertEquals(roundTrip, substring);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(dataProvider = "strings")
|
||||
public void testSubstringCopy(String testString) {
|
||||
if (testString.length() < 3 || !containsOnlyRegularCharacters(testString)) {
|
||||
return;
|
||||
}
|
||||
for (var charset : singleByteCharsets()) {
|
||||
for (var arena: arenas()) {
|
||||
try (arena) {
|
||||
for (int srcIndex = 0; srcIndex <= testString.length(); srcIndex++) {
|
||||
for (int numChars = 0; numChars <= testString.length() - srcIndex; numChars++) {
|
||||
String substring = testString.substring(srcIndex, srcIndex + numChars);
|
||||
long length = substring.getBytes(charset).length;
|
||||
MemorySegment text = arena.allocate(JAVA_BYTE, length);
|
||||
long copied = MemorySegment.copy(testString, charset, srcIndex, text, 0, numChars);
|
||||
String roundTrip = text.getString(0, charset, length);
|
||||
assertEquals(roundTrip, substring);
|
||||
assertEquals(copied, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final MemoryLayout CHAR_POINTER = ADDRESS
|
||||
.withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE));
|
||||
private static final Linker LINKER = Linker.nativeLinker();
|
||||
@ -402,7 +605,7 @@ public class TestStringEncoding {
|
||||
{""},
|
||||
{"X"},
|
||||
{"12345"},
|
||||
{"yen \u00A5"},
|
||||
{"section \u00A7"},
|
||||
{"snowman \u26C4"},
|
||||
{"rainbow \uD83C\uDF08"},
|
||||
{"0"},
|
||||
|
||||
173
test/jdk/javax/swing/JSplitPane/TestSplitPaneCompResize.java
Normal file
173
test/jdk/javax/swing/JSplitPane/TestSplitPaneCompResize.java
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2026, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 4765299
|
||||
* @key headful
|
||||
* @summary Verifies componentResized() is called with nested JSplitPanes
|
||||
* @run main TestSplitPaneCompResize
|
||||
*/
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Point;
|
||||
import java.awt.Robot;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.plaf.basic.BasicSplitPaneDivider;
|
||||
import javax.swing.plaf.basic.BasicSplitPaneUI;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
public class TestSplitPaneCompResize {
|
||||
|
||||
private static JFrame frame;
|
||||
private JSplitPane outer;
|
||||
private static JButton leftOneTouchButton;
|
||||
private static volatile Point leftBtnPos;
|
||||
private static volatile boolean resized;
|
||||
|
||||
public TestSplitPaneCompResize() {
|
||||
|
||||
// set up a simple list embedded inside a scroll pane
|
||||
String[] listItems = {"Item1", "Item2"};
|
||||
JList list = new JList<>(listItems);
|
||||
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
list.setSelectedIndex(0);
|
||||
|
||||
JScrollPane comp = new JScrollPane(list);
|
||||
JSplitPane inner = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
|
||||
comp, new JPanel());
|
||||
JPanel rightPanel = new JPanel();
|
||||
|
||||
outer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
|
||||
inner, rightPanel);
|
||||
outer.setDividerLocation(150);
|
||||
|
||||
//Provide minimum sizes for the two components in the split pane
|
||||
Dimension minimumSize = new Dimension(100, 50);
|
||||
comp.setMinimumSize(minimumSize);
|
||||
inner.setMinimumSize(minimumSize);
|
||||
rightPanel.setMinimumSize(minimumSize);
|
||||
|
||||
//Provide a preferred size for the split pane
|
||||
outer.setPreferredSize(new Dimension(400, 200));
|
||||
inner.addComponentListener(new ComponentAdapter() {
|
||||
public void componentResized(ComponentEvent e) {
|
||||
System.out.println("inner resized");
|
||||
}
|
||||
});
|
||||
comp.addComponentListener(new ComponentAdapter() {
|
||||
public void componentResized(ComponentEvent e) {
|
||||
resized = true;
|
||||
System.out.println("comp resized");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public JSplitPane getSplitPane() {
|
||||
return outer;
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] s) throws Exception {
|
||||
Robot robot = new Robot();
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
frame = new JFrame("SplitPaneDemo");
|
||||
|
||||
TestSplitPaneCompResize sp = new TestSplitPaneCompResize();
|
||||
JSplitPane jsp = sp.getSplitPane();
|
||||
frame.getContentPane().add(jsp);
|
||||
jsp.setUI(new MySplitPaneUI());
|
||||
jsp.setOneTouchExpandable(true);
|
||||
frame.pack();
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setVisible(true);
|
||||
});
|
||||
|
||||
robot.waitForIdle();
|
||||
robot.delay(1000);
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
leftBtnPos = leftOneTouchButton.getLocationOnScreen();
|
||||
leftBtnPos.x += leftOneTouchButton.getWidth() / 2;
|
||||
leftBtnPos.y += leftOneTouchButton.getHeight() / 2;
|
||||
});
|
||||
|
||||
resized = false;
|
||||
robot.mouseMove(leftBtnPos.x, leftBtnPos.y);
|
||||
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
||||
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
||||
robot.waitForIdle();
|
||||
robot.delay(1000);
|
||||
|
||||
if (!resized) {
|
||||
throw new RuntimeException("ComponentResized not called");
|
||||
}
|
||||
} finally {
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
if (frame != null) {
|
||||
frame.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class MySplitPaneUI extends BasicSplitPaneUI {
|
||||
|
||||
public MySplitPaneUI() {
|
||||
super();
|
||||
}
|
||||
|
||||
public BasicSplitPaneDivider createDefaultDivider() {
|
||||
return new MySplitPaneDivider(this);
|
||||
}
|
||||
}
|
||||
|
||||
static class MySplitPaneDivider extends BasicSplitPaneDivider {
|
||||
|
||||
public MySplitPaneDivider(BasicSplitPaneUI ui) {
|
||||
super(ui);
|
||||
}
|
||||
|
||||
protected JButton createLeftOneTouchButton() {
|
||||
leftOneTouchButton = super.createLeftOneTouchButton();
|
||||
return leftOneTouchButton;
|
||||
}
|
||||
|
||||
protected JButton createRightOneTouchButton() {
|
||||
JButton rightOneTouchButton = super.createRightOneTouchButton();
|
||||
return rightOneTouchButton;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -195,6 +195,7 @@ class InstructionValidationTest {
|
||||
ensureFailFast(i, cob -> cob.iinc(i, 1));
|
||||
}
|
||||
check(fails, () -> IncrementInstruction.of(i, 1));
|
||||
check(fails, () -> IncrementInstruction.of(IINC_W, i, 1));
|
||||
check(fails, () -> DiscontinuedInstruction.RetInstruction.of(i));
|
||||
check(fails, () -> DiscontinuedInstruction.RetInstruction.of(RET_W, i));
|
||||
check(fails, () -> LocalVariable.of(i, "test", CD_Object, dummyLabel, dummyLabel));
|
||||
@ -208,6 +209,7 @@ class InstructionValidationTest {
|
||||
check(fails, () -> LoadInstruction.of(u1Op, i));
|
||||
for (var u1Op : List.of(ASTORE, ISTORE, LSTORE, FSTORE, DSTORE))
|
||||
check(fails, () -> StoreInstruction.of(u1Op, i));
|
||||
check(fails, () -> IncrementInstruction.of(IINC, i, 1));
|
||||
check(fails, () -> DiscontinuedInstruction.RetInstruction.of(RET, i));
|
||||
}
|
||||
|
||||
@ -250,6 +252,13 @@ class InstructionValidationTest {
|
||||
IncrementInstruction.of(0, 2);
|
||||
IncrementInstruction.of(0, Short.MAX_VALUE);
|
||||
IncrementInstruction.of(0, Short.MIN_VALUE);
|
||||
IncrementInstruction.of(IINC, 0, 2);
|
||||
IncrementInstruction.of(IINC, 0, Byte.MIN_VALUE);
|
||||
IncrementInstruction.of(IINC, 0, Byte.MAX_VALUE);
|
||||
IncrementInstruction.of(IINC_W, 0, 2);
|
||||
IncrementInstruction.of(IINC_W, 0, Short.MIN_VALUE);
|
||||
IncrementInstruction.of(IINC_W, 0, Short.MAX_VALUE);
|
||||
|
||||
for (int i : new int[] {Short.MIN_VALUE - 1, Short.MAX_VALUE + 1}) {
|
||||
assertThrows(IllegalArgumentException.class, () -> IncrementInstruction.of(0, i));
|
||||
TestUtil.runCodeHandler(cob -> {
|
||||
@ -257,6 +266,12 @@ class InstructionValidationTest {
|
||||
cob.return_();
|
||||
});
|
||||
}
|
||||
for (int i : new int[] {Byte.MIN_VALUE - 1, Byte.MAX_VALUE + 1}) {
|
||||
assertThrows(IllegalArgumentException.class, () -> IncrementInstruction.of(IINC, 0, i));
|
||||
}
|
||||
for (int i : new int[] {Short.MIN_VALUE - 1, Short.MAX_VALUE + 1}) {
|
||||
assertThrows(IllegalArgumentException.class, () -> IncrementInstruction.of(IINC_W, 0, i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -34,8 +34,9 @@ import jdk.jfr.Configuration;
|
||||
import jdk.jfr.Event;
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.StackTrace;
|
||||
import jdk.jfr.consumer.EventStream;
|
||||
import jdk.jfr.consumer.RecordedClassLoader;
|
||||
import jdk.jfr.consumer.RecordingStream;
|
||||
import jdk.test.lib.jfr.TestClassLoader;
|
||||
|
||||
/**
|
||||
* @test
|
||||
@ -52,28 +53,17 @@ public class TestBackToBackSensitive {
|
||||
static class FillEvent extends Event {
|
||||
String message;
|
||||
}
|
||||
public static Object OBJECT;
|
||||
|
||||
public static void main(String... arg) throws Exception {
|
||||
Set<Instant> threadDumps = Collections.synchronizedSet(new LinkedHashSet<>());
|
||||
Set<Instant> classLoaderStatistics = Collections.synchronizedSet(new LinkedHashSet<>());
|
||||
Set<Instant> physicalMemory = Collections.synchronizedSet(new LinkedHashSet<>());
|
||||
|
||||
TestClassLoader loader = new TestClassLoader();
|
||||
Class<?> clazz = loader.loadClass(TestBackToBackSensitive.class.getName());
|
||||
String classLoaderName = loader.getClass().getName();
|
||||
OBJECT = clazz.getDeclaredConstructor().newInstance();
|
||||
Configuration configuration = Configuration.getConfiguration("default");
|
||||
try (RecordingStream r1 = new RecordingStream(configuration)) {
|
||||
r1.setMaxSize(Long.MAX_VALUE);
|
||||
r1.onEvent("jdk.ThreadDump", e -> threadDumps.add(e.getStartTime()));
|
||||
r1.onEvent("jdk.ClassLoaderStatistics", e -> {
|
||||
RecordedClassLoader cl = e.getValue("classLoader");
|
||||
if (cl != null) {
|
||||
if (cl.getType().getName().contains("PlatformClassLoader")) {
|
||||
classLoaderStatistics.add(e.getStartTime());
|
||||
System.out.println("Class loader" + e);
|
||||
}
|
||||
}
|
||||
});
|
||||
r1.onEvent("jdk.PhysicalMemory", e -> physicalMemory.add(e.getStartTime()));
|
||||
try (Recording r1 = new Recording(configuration)) {
|
||||
// Start chunk 1
|
||||
r1.startAsync();
|
||||
r1.start();
|
||||
try (Recording r2 = new Recording()) {
|
||||
// Start chunk 2
|
||||
r2.start();
|
||||
@ -86,6 +76,25 @@ public class TestBackToBackSensitive {
|
||||
f.commit();
|
||||
}
|
||||
r1.stop();
|
||||
Path file = Path.of("file.jfr");
|
||||
r1.dump(file);
|
||||
Set<Instant> threadDumps = new LinkedHashSet<>();
|
||||
Set<Instant> classLoaderStatistics = new LinkedHashSet<>();
|
||||
Set<Instant> physicalMemory = new LinkedHashSet<>();
|
||||
try (EventStream es = EventStream.openFile(file)) {
|
||||
es.onEvent("jdk.ThreadDump", e -> threadDumps.add(e.getStartTime()));
|
||||
es.onEvent("jdk.ClassLoaderStatistics", e -> {
|
||||
RecordedClassLoader cl = e.getValue("classLoader");
|
||||
if (cl != null) {
|
||||
if (cl.getType().getName().equals(classLoaderName)) {
|
||||
classLoaderStatistics.add(e.getStartTime());
|
||||
System.out.println("Class loader" + e);
|
||||
}
|
||||
}
|
||||
});
|
||||
es.onEvent("jdk.PhysicalMemory", e -> physicalMemory.add(e.getStartTime()));
|
||||
es.start();
|
||||
}
|
||||
long chunkFiles = filesInRepository();
|
||||
System.out.println("Number of chunk files: " + chunkFiles);
|
||||
// When jdk.PhysicalMemory is expected to be emitted:
|
||||
@ -93,15 +102,15 @@ public class TestBackToBackSensitive {
|
||||
// Chunk 2: begin, end
|
||||
// Chunk 3: begin, end
|
||||
// Chunk 4: begin, end
|
||||
assertCount(r1, "jdk.PhysicalMemory", physicalMemory, 2 * chunkFiles);
|
||||
assertCount("jdk.PhysicalMemory", physicalMemory, 2 * chunkFiles);
|
||||
// When jdk.ClassLoaderStatistics and jdk.ThreadThreadDump are expected to be
|
||||
// emitted:
|
||||
// Chunk 1: begin, end
|
||||
// Chunk 2: begin, end
|
||||
// Chunk 3: end
|
||||
// Chunk 4: end
|
||||
assertCount(r1, "jdk.ThreadDump", threadDumps, 2 + 2 + (chunkFiles - 2));
|
||||
assertCount(r1, "jdk.ClassLoaderStatistics", classLoaderStatistics, 2 + 2 + (chunkFiles - 2));
|
||||
assertCount("jdk.ThreadDump", threadDumps, 2 + 2 + (chunkFiles - 2));
|
||||
assertCount("jdk.ClassLoaderStatistics", classLoaderStatistics, 2 + 2 + (chunkFiles - 2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,15 +119,13 @@ public class TestBackToBackSensitive {
|
||||
return Files.list(repository).filter(p -> p.toString().endsWith(".jfr")).count();
|
||||
}
|
||||
|
||||
private static void assertCount(RecordingStream stream, String eventName, Set<Instant> timestamps, long expected) throws Exception {
|
||||
private static void assertCount(String eventName, Set<Instant> timestamps, long expected) throws Exception {
|
||||
System.out.println("Timestamps for " + eventName + ":");
|
||||
for (Instant timestamp : timestamps) {
|
||||
System.out.println(timestamp);
|
||||
}
|
||||
int count = timestamps.size();
|
||||
if (count != expected) {
|
||||
System.out.println("Dumping failure file.");
|
||||
stream.dump(Path.of("failure.jfr"));
|
||||
throw new Exception("Expected " + expected + " timestamps for event " + eventName + ", but got " + count);
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
public record AppImageFile(String mainLauncherName, Optional<String> mainLauncherClassName,
|
||||
String version, boolean macSigned, boolean macAppStore, Map<String, Map<String, String>> launchers) {
|
||||
String version, boolean macAppStore, Map<String, Map<String, String>> launchers) {
|
||||
|
||||
public static Path getPathInAppImage(Path appImageDir) {
|
||||
return ApplicationLayout.platformAppImage()
|
||||
@ -66,7 +66,7 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
||||
}
|
||||
|
||||
public AppImageFile(String mainLauncherName, Optional<String> mainLauncherClassName) {
|
||||
this(mainLauncherName, mainLauncherClassName, "1.0", false, false, Map.of(mainLauncherName, Map.of()));
|
||||
this(mainLauncherName, mainLauncherClassName, "1.0", false, Map.of(mainLauncherName, Map.of()));
|
||||
}
|
||||
|
||||
public AppImageFile(String mainLauncherName, String mainLauncherClassName) {
|
||||
@ -103,10 +103,6 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
||||
xml.writeEndElement();
|
||||
}));
|
||||
|
||||
xml.writeStartElement("signed");
|
||||
xml.writeCharacters(Boolean.toString(macSigned));
|
||||
xml.writeEndElement();
|
||||
|
||||
xml.writeStartElement("app-store");
|
||||
xml.writeCharacters(Boolean.toString(macAppStore));
|
||||
xml.writeEndElement();
|
||||
@ -140,10 +136,6 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
||||
var mainLauncherClassName = Optional.ofNullable(xPath.evaluate(
|
||||
"/jpackage-state/main-class/text()", doc));
|
||||
|
||||
var macSigned = Optional.ofNullable(xPath.evaluate(
|
||||
"/jpackage-state/signed/text()", doc)).map(
|
||||
Boolean::parseBoolean).orElse(false);
|
||||
|
||||
var macAppStore = Optional.ofNullable(xPath.evaluate(
|
||||
"/jpackage-state/app-store/text()", doc)).map(
|
||||
Boolean::parseBoolean).orElse(false);
|
||||
@ -171,7 +163,6 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
||||
mainLauncherName,
|
||||
mainLauncherClassName,
|
||||
version,
|
||||
macSigned,
|
||||
macAppStore,
|
||||
Collections.unmodifiableMap(launchers));
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.spi.ToolProvider;
|
||||
import java.util.stream.IntStream;
|
||||
@ -109,15 +110,6 @@ public final class Executor extends CommandArguments<Executor> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Executor setWinRunWithEnglishOutput(boolean value) {
|
||||
if (!TKit.isWindows()) {
|
||||
throw new UnsupportedOperationException(
|
||||
"setWinRunWithEnglishOutput is only valid on Windows platform");
|
||||
}
|
||||
winEnglishOutput = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Executor setWindowsTmpDir(String tmp) {
|
||||
if (!TKit.isWindows()) {
|
||||
throw new UnsupportedOperationException(
|
||||
@ -195,6 +187,11 @@ public final class Executor extends CommandArguments<Executor> {
|
||||
return storeOutputInFiles(true);
|
||||
}
|
||||
|
||||
public Executor processListener(Consumer<Process> v) {
|
||||
commandOutputControl.processListener(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
public record Result(CommandOutputControl.Result base) {
|
||||
public Result {
|
||||
Objects.requireNonNull(base);
|
||||
@ -310,11 +307,6 @@ public final class Executor extends CommandArguments<Executor> {
|
||||
"Can't change directory when using tool provider");
|
||||
}
|
||||
|
||||
if (toolProvider != null && winEnglishOutput) {
|
||||
throw new IllegalArgumentException(
|
||||
"Can't change locale when using tool provider");
|
||||
}
|
||||
|
||||
return ThrowingSupplier.toSupplier(() -> {
|
||||
if (toolProvider != null) {
|
||||
return runToolProvider();
|
||||
@ -434,17 +426,8 @@ public final class Executor extends CommandArguments<Executor> {
|
||||
return executable.toAbsolutePath();
|
||||
}
|
||||
|
||||
private List<String> prefixCommandLineArgs() {
|
||||
if (winEnglishOutput) {
|
||||
return List.of("cmd.exe", "/c", "chcp", "437", ">nul", "2>&1", "&&");
|
||||
} else {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
private Result runExecutable() throws IOException, InterruptedException {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.addAll(prefixCommandLineArgs());
|
||||
command.add(executablePath().toString());
|
||||
command.addAll(args);
|
||||
ProcessBuilder builder = new ProcessBuilder(command);
|
||||
@ -522,8 +505,7 @@ public final class Executor extends CommandArguments<Executor> {
|
||||
exec = executablePath().toString();
|
||||
}
|
||||
|
||||
var cmdline = Stream.of(prefixCommandLineArgs(), List.of(exec), args).flatMap(
|
||||
List::stream).toList();
|
||||
var cmdline = Stream.of(List.of(exec), args).flatMap(List::stream).toList();
|
||||
|
||||
return String.format(format, CommandLineFormat.DEFAULT.apply(cmdline), cmdline.size());
|
||||
}
|
||||
@ -559,6 +541,5 @@ public final class Executor extends CommandArguments<Executor> {
|
||||
private Path directory;
|
||||
private Set<String> removeEnvVars = new HashSet<>();
|
||||
private Map<String, String> setEnvVars = new HashMap<>();
|
||||
private boolean winEnglishOutput;
|
||||
private String winTmpDir = null;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2026, 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
|
||||
@ -35,6 +35,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
@ -315,37 +316,33 @@ public final class HelloApp {
|
||||
|
||||
public static void executeLauncherAndVerifyOutput(JPackageCommand cmd,
|
||||
String... args) {
|
||||
AppOutputVerifier av = assertMainLauncher(cmd, args);
|
||||
if (av != null) {
|
||||
assertMainLauncher(cmd, args).ifPresent(av -> {
|
||||
av.executeAndVerifyOutput(args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Executor.Result executeLauncher(JPackageCommand cmd,
|
||||
String... args) {
|
||||
AppOutputVerifier av = assertMainLauncher(cmd, args);
|
||||
if (av != null) {
|
||||
return assertMainLauncher(cmd, args).map(av -> {
|
||||
return av.saveOutput(true).execute(args);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).orElseThrow();
|
||||
}
|
||||
|
||||
public static AppOutputVerifier assertMainLauncher(JPackageCommand cmd,
|
||||
public static Optional<AppOutputVerifier> assertMainLauncher(JPackageCommand cmd,
|
||||
String... args) {
|
||||
final Path launcherPath = cmd.appLauncherPath();
|
||||
if (!cmd.canRunLauncher(String.format("Not running [%s] launcher",
|
||||
launcherPath))) {
|
||||
return null;
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return assertApp(launcherPath)
|
||||
return Optional.of(assertApp(launcherPath)
|
||||
.addDefaultArguments(Optional
|
||||
.ofNullable(cmd.getAllArgumentValues("--arguments"))
|
||||
.orElseGet(() -> new String[0]))
|
||||
.addJavaOptions(Optional
|
||||
.ofNullable(cmd.getAllArgumentValues("--java-options"))
|
||||
.orElseGet(() -> new String[0]));
|
||||
.orElseGet(() -> new String[0])));
|
||||
}
|
||||
|
||||
|
||||
@ -426,6 +423,11 @@ public final class HelloApp {
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public AppOutputVerifier processListener(Consumer<Process> v) {
|
||||
processListener = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void verifyOutput(String... args) {
|
||||
final List<String> launcherArgs = List.of(args);
|
||||
final List<String> appArgs;
|
||||
@ -479,6 +481,7 @@ public final class HelloApp {
|
||||
.saveOutput(saveOutput)
|
||||
.dumpOutput()
|
||||
.setExecutable(executablePath)
|
||||
.processListener(processListener)
|
||||
.addArguments(List.of(args));
|
||||
|
||||
env.forEach((envVarName, envVarValue) -> {
|
||||
@ -493,6 +496,7 @@ public final class HelloApp {
|
||||
private final Path launcherPath;
|
||||
private Path outputFilePath;
|
||||
private int expectedExitCode;
|
||||
private Consumer<Process> processListener;
|
||||
private final List<String> defaultLauncherArgs;
|
||||
private final Map<String, String> params;
|
||||
private final Map<String, String> env;
|
||||
|
||||
@ -1385,7 +1385,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
if (!isImagePackageType() && hasArgument("--app-image")) {
|
||||
// Build native macOS package from an external app image.
|
||||
// If the external app image is signed, ".jpackage.xml" file should be kept, otherwise removed.
|
||||
return AppImageFile.load(Path.of(getArgumentValue("--app-image"))).macSigned();
|
||||
return MacHelper.isBundleSigned(Path.of(getArgumentValue("--app-image")));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1406,13 +1406,8 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
final AppImageFile aif = AppImageFile.load(rootDir);
|
||||
|
||||
if (TKit.isOSX()) {
|
||||
boolean expectedValue = MacHelper.appImageSigned(this);
|
||||
boolean actualValue = aif.macSigned();
|
||||
TKit.assertEquals(expectedValue, actualValue,
|
||||
"Check for unexpected value of <signed> property in app image file");
|
||||
|
||||
expectedValue = hasArgument("--mac-app-store");
|
||||
actualValue = aif.macAppStore();
|
||||
var expectedValue = hasArgument("--mac-app-store");
|
||||
var actualValue = aif.macAppStore();
|
||||
TKit.assertEquals(expectedValue, actualValue,
|
||||
"Check for unexpected value of <app-store> property in app image file");
|
||||
}
|
||||
@ -1437,7 +1432,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
} else {
|
||||
if (TKit.isOSX() && hasArgument("--app-image")) {
|
||||
String appImage = getArgumentValue("--app-image");
|
||||
if (AppImageFile.load(Path.of(appImage)).macSigned()) {
|
||||
if (MacHelper.isBundleSigned(Path.of(appImage))) {
|
||||
assertFileNotInAppImage(lookupPath);
|
||||
} else {
|
||||
assertFileInAppImage(lookupPath);
|
||||
|
||||
@ -33,6 +33,7 @@ import static jdk.jpackage.internal.util.PListWriter.writeStringArray;
|
||||
import static jdk.jpackage.internal.util.PListWriter.writeStringOptional;
|
||||
import static jdk.jpackage.internal.util.XmlUtils.initDocumentBuilder;
|
||||
import static jdk.jpackage.internal.util.XmlUtils.toXmlConsumer;
|
||||
import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction;
|
||||
import static jdk.jpackage.internal.util.function.ThrowingRunnable.toRunnable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@ -45,6 +46,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -59,14 +61,13 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.XMLStreamWriter;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import jdk.jpackage.internal.util.FileUtils;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
import jdk.jpackage.internal.util.PListReader;
|
||||
import jdk.jpackage.internal.util.PathUtils;
|
||||
import jdk.jpackage.internal.util.RetryExecutor;
|
||||
@ -89,8 +90,8 @@ public final class MacHelper {
|
||||
// See JDK-8373105. "hdiutil" does not handle such cases very good.
|
||||
final var mountRoot = TKit.createTempDirectory("mountRoot");
|
||||
|
||||
// Explode DMG assuming this can require interaction, thus use `yes`.
|
||||
final var attachStdout = Executor.of("sh", "-c", String.join(" ",
|
||||
// Explode the DMG assuming this can require interaction if the DMG has a license, thus use `yes`.
|
||||
final var attachExec = Executor.of("sh", "-c", String.join(" ",
|
||||
"yes",
|
||||
"|",
|
||||
"/usr/bin/hdiutil",
|
||||
@ -99,14 +100,34 @@ public final class MacHelper {
|
||||
"-mountroot", PathUtils.normalizedAbsolutePathString(mountRoot),
|
||||
"-nobrowse",
|
||||
"-plist"
|
||||
)).saveOutput().storeOutputInFiles().executeAndRepeatUntilExitCode(0, 10, 6).stdout();
|
||||
)).saveOutput().storeOutputInFiles().binaryOutput();
|
||||
|
||||
final var attachResult = attachExec.executeAndRepeatUntilExitCode(0, 10, 6);
|
||||
|
||||
final Path mountPoint;
|
||||
|
||||
boolean mountPointInitialized = false;
|
||||
try {
|
||||
byte[] stdout = attachResult.byteStdout();
|
||||
|
||||
// If the DMG has a license, it will be printed to the stdout before the plist content.
|
||||
// All bytes before the XML declaration of the plist must be skipped.
|
||||
// We need to find the location of the {'<', '?', 'x', 'm', 'l'} byte array
|
||||
// (the XML declaration) in the captured binary stdout.
|
||||
// Instead of crafting an ad-hoc function that operates on byte arrays,
|
||||
// we will convert the byte array into a String instance using
|
||||
// an 8-bit character set (ISO-8859-1) and use the standard String#indexOf().
|
||||
var startPlistIndex = new String(stdout, StandardCharsets.ISO_8859_1).indexOf("<?xml");
|
||||
|
||||
byte[] plistXml;
|
||||
if (startPlistIndex > 0) {
|
||||
plistXml = Arrays.copyOfRange(stdout, startPlistIndex, stdout.length);
|
||||
} else {
|
||||
plistXml = stdout;
|
||||
}
|
||||
|
||||
// One of "dict" items of "system-entities" array property should contain "mount-point" string property.
|
||||
mountPoint = readPList(attachStdout).queryArrayValue("system-entities", false)
|
||||
mountPoint = readPList(plistXml).queryArrayValue("system-entities", false)
|
||||
.map(PListReader.class::cast)
|
||||
.map(dict -> {
|
||||
return dict.findValue("mount-point");
|
||||
@ -117,7 +138,7 @@ public final class MacHelper {
|
||||
} finally {
|
||||
if (!mountPointInitialized) {
|
||||
TKit.trace("Unexpected plist file missing `system-entities` array:");
|
||||
attachStdout.forEach(TKit::trace);
|
||||
attachResult.toCharacterResult(attachExec.charset(), false).stdout().forEach(TKit::trace);
|
||||
TKit.trace("Done");
|
||||
}
|
||||
}
|
||||
@ -168,19 +189,13 @@ public final class MacHelper {
|
||||
|
||||
public static PListReader readPList(Path path) {
|
||||
TKit.assertReadableFileExists(path);
|
||||
return ThrowingSupplier.toSupplier(() -> readPList(Files.readAllLines(
|
||||
path))).get();
|
||||
return readPList(toFunction(Files::readAllBytes).apply(path));
|
||||
}
|
||||
|
||||
public static PListReader readPList(List<String> lines) {
|
||||
return readPList(lines.stream());
|
||||
}
|
||||
|
||||
public static PListReader readPList(Stream<String> lines) {
|
||||
return ThrowingSupplier.toSupplier(() -> new PListReader(lines
|
||||
// Skip leading lines before xml declaration
|
||||
.dropWhile(Pattern.compile("\\s?<\\?xml\\b.+\\?>").asPredicate().negate())
|
||||
.collect(Collectors.joining()).getBytes(StandardCharsets.UTF_8))).get();
|
||||
public static PListReader readPList(byte[] xml) {
|
||||
return ThrowingSupplier.toSupplier(() -> {
|
||||
return new PListReader(xml);
|
||||
}).get();
|
||||
}
|
||||
|
||||
public static Map<String, String> flatMapPList(PListReader plistReader) {
|
||||
@ -265,13 +280,13 @@ public final class MacHelper {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
var runtimeImage = Optional.ofNullable(cmd.getArgumentValue("--runtime-image")).map(Path::of);
|
||||
var runtimeImageBundle = Optional.ofNullable(cmd.getArgumentValue("--runtime-image")).map(Path::of).flatMap(MacBundle::fromPath);
|
||||
var appImage = Optional.ofNullable(cmd.getArgumentValue("--app-image")).map(Path::of);
|
||||
|
||||
if (cmd.isRuntime() && Files.isDirectory(runtimeImage.orElseThrow().resolve("Contents/_CodeSignature"))) {
|
||||
if (cmd.isRuntime() && runtimeImageBundle.map(MacHelper::isBundleSigned).orElse(false)) {
|
||||
// If the predefined runtime is a signed bundle, bundled image should be signed too.
|
||||
return true;
|
||||
} else if (appImage.map(AppImageFile::load).map(AppImageFile::macSigned).orElse(false)) {
|
||||
} else if (appImage.map(MacHelper::isBundleSigned).orElse(false)) {
|
||||
// The external app image is signed, so the app image is signed too.
|
||||
return true;
|
||||
}
|
||||
@ -301,6 +316,14 @@ public final class MacHelper {
|
||||
}).run();
|
||||
}
|
||||
|
||||
static boolean isBundleSigned(Path bundleRoot) {
|
||||
return isBundleSigned(MacBundle.fromPath(bundleRoot).orElseThrow(IllegalArgumentException::new));
|
||||
}
|
||||
|
||||
static boolean isBundleSigned(MacBundle bundle) {
|
||||
return MacSignVerify.findSpctlSignOrigin(MacSignVerify.SpctlType.EXEC, bundle.root(), true).isPresent();
|
||||
}
|
||||
|
||||
private static void createFaPListFragmentFromFaProperties(JPackageCommand cmd, XMLStreamWriter xml)
|
||||
throws XMLStreamException, IOException {
|
||||
|
||||
@ -383,7 +406,7 @@ public final class MacHelper {
|
||||
|
||||
var predefinedAppImage = Path.of(Optional.ofNullable(cmd.getArgumentValue("--app-image")).orElseThrow(IllegalArgumentException::new));
|
||||
|
||||
var plistPath = ApplicationLayout.macAppImage().resolveAt(predefinedAppImage).contentDirectory().resolve("Info.plist");
|
||||
var plistPath = MacBundle.fromPath(predefinedAppImage).orElseThrow().infoPlistFile();
|
||||
|
||||
try (var plistStream = Files.newInputStream(plistPath)) {
|
||||
var plist = new PListReader(initDocumentBuilder().parse(plistStream));
|
||||
|
||||
@ -22,7 +22,6 @@
|
||||
*/
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
|
||||
import static jdk.jpackage.test.MacSign.DigestAlgorithm.SHA256;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@ -30,10 +29,8 @@ import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import jdk.jpackage.internal.util.PListReader;
|
||||
import jdk.jpackage.test.MacSign.CertificateHash;
|
||||
@ -66,7 +63,7 @@ public final class MacSignVerify {
|
||||
});
|
||||
|
||||
// Set to "null" if the sign origin is not found, instead of bailing out with an exception.
|
||||
// Let is fail in the following TKit.assertEquals() call with a proper log message.
|
||||
// Let it fail in the following TKit.assertEquals() call with a proper log message.
|
||||
var signOrigin = findSpctlSignOrigin(SpctlType.EXEC, bundleRoot).orElse(null);
|
||||
|
||||
TKit.assertEquals(certRequest.name(), signOrigin,
|
||||
@ -92,10 +89,14 @@ public final class MacSignVerify {
|
||||
}
|
||||
|
||||
public static Optional<PListReader> findEntitlements(Path path) {
|
||||
final var exec = Executor.of("/usr/bin/codesign", "-d", "--entitlements", "-", "--xml", path.toString()).saveOutput().dumpOutput();
|
||||
final var exec = Executor.of(
|
||||
"/usr/bin/codesign",
|
||||
"-d",
|
||||
"--entitlements", "-",
|
||||
"--xml", path.toString()).saveOutput().dumpOutput().binaryOutput();
|
||||
final var result = exec.execute();
|
||||
var xml = result.stdout();
|
||||
if (xml.isEmpty()) {
|
||||
var xml = result.byteStdout();
|
||||
if (xml.length == 0) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(MacHelper.readPList(xml));
|
||||
@ -135,17 +136,33 @@ public final class MacSignVerify {
|
||||
public static final String ADHOC_SIGN_ORIGIN = "-";
|
||||
|
||||
public static Optional<String> findSpctlSignOrigin(SpctlType type, Path path) {
|
||||
final var exec = Executor.of("/usr/sbin/spctl", "-vv", "--raw", "--assess", "--type", type.value(), path.toString()).saveOutput().discardStderr();
|
||||
final var result = exec.executeWithoutExitCodeCheck();
|
||||
TKit.assertTrue(Set.of(0, 3).contains(result.getExitCode()),
|
||||
String.format("Check exit code of command %s is either 0 or 3", exec.getPrintableCommandLine()));
|
||||
return toSupplier(() -> {
|
||||
try {
|
||||
return Optional.of(new PListReader(String.join("", result.getOutput()).getBytes()).queryValue("assessment:originator"));
|
||||
} catch (NoSuchElementException ex) {
|
||||
return Optional.<String>empty();
|
||||
return findSpctlSignOrigin(type, path, false);
|
||||
}
|
||||
|
||||
public static Optional<String> findSpctlSignOrigin(SpctlType type, Path path, boolean acceptBrokenSignature) {
|
||||
final var exec = Executor.of(
|
||||
"/usr/sbin/spctl",
|
||||
"-vv",
|
||||
"--raw",
|
||||
"--assess",
|
||||
"--type", type.value(),
|
||||
path.toString()).saveOutput().discardStderr().binaryOutput();
|
||||
Executor.Result result;
|
||||
if (acceptBrokenSignature) {
|
||||
result = exec.executeWithoutExitCodeCheck();
|
||||
switch (result.getExitCode()) {
|
||||
case 0, 3 -> {
|
||||
// NOP
|
||||
}
|
||||
default -> {
|
||||
// No plist XML to process.
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}).get();
|
||||
} else {
|
||||
result = exec.execute(0, 3);
|
||||
}
|
||||
return MacHelper.readPList(result.byteStdout()).findValue("assessment:originator");
|
||||
}
|
||||
|
||||
public static Optional<String> findCodesignSignOrigin(Path path) {
|
||||
|
||||
@ -42,8 +42,6 @@ import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
import jdk.jpackage.test.PackageTest.PackageHandlers;
|
||||
@ -306,91 +304,6 @@ public class WindowsHelper {
|
||||
"Failed to get file description of [%s]", pathToExeFile));
|
||||
}
|
||||
|
||||
public static void killProcess(long pid) {
|
||||
Executor.of("taskkill", "/F", "/PID", Long.toString(pid)).dumpOutput(true).execute();
|
||||
}
|
||||
|
||||
public static void killAppLauncherProcess(JPackageCommand cmd,
|
||||
String launcherName, int expectedCount) {
|
||||
var pids = findAppLauncherPIDs(cmd, launcherName);
|
||||
try {
|
||||
TKit.assertEquals(expectedCount, pids.length, String.format(
|
||||
"Check [%d] %s app launcher processes found running",
|
||||
expectedCount, Optional.ofNullable(launcherName).map(
|
||||
str -> "[" + str + "]").orElse("<main>")));
|
||||
} finally {
|
||||
if (pids.length != 0) {
|
||||
killProcess(pids[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long[] findAppLauncherPIDs(JPackageCommand cmd, String launcherName) {
|
||||
// Get the list of PIDs and PPIDs of app launcher processes. Run setWinRunWithEnglishOutput(true) for JDK-8344275.
|
||||
// powershell -NoLogo -NoProfile -NonInteractive -Command
|
||||
// "Get-CimInstance Win32_Process -Filter \"Name = 'foo.exe'\" | select ProcessID,ParentProcessID"
|
||||
String command = "Get-CimInstance Win32_Process -Filter \\\"Name = '"
|
||||
+ cmd.appLauncherPath(launcherName).getFileName().toString()
|
||||
+ "'\\\" | select ProcessID,ParentProcessID";
|
||||
List<String> output = Executor.of("powershell", "-NoLogo", "-NoProfile", "-NonInteractive", "-Command", command)
|
||||
.dumpOutput(true).saveOutput().setWinRunWithEnglishOutput(true).executeAndGetOutput();
|
||||
|
||||
if (output.size() < 1) {
|
||||
return new long[0];
|
||||
}
|
||||
|
||||
String[] headers = Stream.of(output.get(1).split("\\s+", 2)).map(
|
||||
String::trim).map(String::toLowerCase).toArray(String[]::new);
|
||||
Pattern pattern;
|
||||
if (headers[0].equals("parentprocessid") && headers[1].equals(
|
||||
"processid")) {
|
||||
pattern = Pattern.compile("^\\s+(?<ppid>\\d+)\\s+(?<pid>\\d+)$");
|
||||
} else if (headers[1].equals("parentprocessid") && headers[0].equals(
|
||||
"processid")) {
|
||||
pattern = Pattern.compile("^\\s+(?<pid>\\d+)\\s+(?<ppid>\\d+)$");
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Unrecognizable output of \'Get-CimInstance Win32_Process\' command");
|
||||
}
|
||||
|
||||
List<long[]> processes = output.stream().skip(3).map(line -> {
|
||||
Matcher m = pattern.matcher(line);
|
||||
long[] pids = null;
|
||||
if (m.matches()) {
|
||||
pids = new long[]{Long.parseLong(m.group("pid")), Long.
|
||||
parseLong(m.group("ppid"))};
|
||||
}
|
||||
return pids;
|
||||
}).filter(Objects::nonNull).toList();
|
||||
|
||||
switch (processes.size()) {
|
||||
case 2 -> {
|
||||
final long parentPID;
|
||||
final long childPID;
|
||||
if (processes.get(0)[0] == processes.get(1)[1]) {
|
||||
parentPID = processes.get(0)[0];
|
||||
childPID = processes.get(1)[0];
|
||||
} else if (processes.get(1)[0] == processes.get(0)[1]) {
|
||||
parentPID = processes.get(1)[0];
|
||||
childPID = processes.get(0)[0];
|
||||
} else {
|
||||
TKit.assertUnexpected("App launcher processes unrelated");
|
||||
return null; // Unreachable
|
||||
}
|
||||
return new long[]{parentPID, childPID};
|
||||
}
|
||||
case 1 -> {
|
||||
return new long[]{processes.get(0)[0]};
|
||||
}
|
||||
default -> {
|
||||
TKit.assertUnexpected(String.format(
|
||||
"Unexpected number of running processes [%d]",
|
||||
processes.size()));
|
||||
return null; // Unreachable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isUserLocalInstall(JPackageCommand cmd) {
|
||||
return cmd.hasArgument("--win-per-user-install");
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2026, 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
|
||||
@ -29,7 +29,6 @@ import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_AS_S
|
||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_NAME;
|
||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LINUX_LAUNCHER_SHORTCUT;
|
||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_APP_STORE;
|
||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_SIGNED;
|
||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.WIN_LAUNCHER_DESKTOP_SHORTCUT;
|
||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.WIN_LAUNCHER_MENU_SHORTCUT;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.APPCLASS;
|
||||
@ -514,7 +513,6 @@ public class AppImageFileTest {
|
||||
"<main-class>Foo</main-class>",
|
||||
"<y/>",
|
||||
"<x>property-x</x>",
|
||||
"<signed>true</signed>",
|
||||
"<app-store>False</app-store>",
|
||||
"<add-launcher name='add-launcher'>",
|
||||
" <description>Quick brown fox</description>",
|
||||
@ -546,8 +544,7 @@ public class AppImageFileTest {
|
||||
.addExtra(WIN_LAUNCHER_MENU_SHORTCUT, new LauncherShortcut(LauncherShortcutStartupDirectory.APP_DIR)).commit()).create());
|
||||
|
||||
testCases.add(builder.os(OperatingSystem.MACOS).expect(appBuilder.get().commit()
|
||||
.addExtra(MAC_APP_STORE, false)
|
||||
.addExtra(MAC_SIGNED, true)).create());
|
||||
.addExtra(MAC_APP_STORE, false)).create());
|
||||
|
||||
return testCases;
|
||||
}
|
||||
@ -580,7 +577,6 @@ public class AppImageFileTest {
|
||||
"<main-class>OverwrittenMain</main-class>",
|
||||
"<main-class>Main</main-class>",
|
||||
"<x>property-x</x>",
|
||||
"<signed>true</signed>",
|
||||
"<add-launcher name='service-launcher' service='true'>",
|
||||
" <linux-shortcut><nested>foo</nested></linux-shortcut>",
|
||||
" <description>service-launcher description</description>",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -34,6 +34,8 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.StreamSupport;
|
||||
import jdk.jpackage.internal.cli.Validator.ParsedValue;
|
||||
import jdk.jpackage.internal.cli.Validator.ValidatorException;
|
||||
import jdk.jpackage.test.JUnitUtils;
|
||||
|
||||
final class TestUtils {
|
||||
@ -152,6 +154,31 @@ final class TestUtils {
|
||||
}
|
||||
|
||||
|
||||
static final class RecordingValidator<T, U extends Exception> implements Validator<T, U> {
|
||||
|
||||
RecordingValidator(Validator<T, U> validator) {
|
||||
this.validator = Objects.requireNonNull(validator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<U> validate(OptionName optionName, ParsedValue<T> optionValue) {
|
||||
counter++;
|
||||
return validator.validate(optionName, optionValue);
|
||||
}
|
||||
|
||||
int counter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
void resetCounter() {
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
private final Validator<T, U> validator;
|
||||
private int counter;
|
||||
}
|
||||
|
||||
|
||||
private record RecordingExceptionFactory(OptionValueExceptionFactory<? extends RuntimeException> factory,
|
||||
Consumer<OptionFailure> sink) implements OptionValueExceptionFactory<RuntimeException> {
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -32,10 +32,11 @@ import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.cli.TestUtils.TestException;
|
||||
import jdk.jpackage.internal.cli.TestUtils.RecordingValidator;
|
||||
import jdk.jpackage.internal.cli.Validator.ParsedValue;
|
||||
import jdk.jpackage.internal.cli.Validator.ValidatingConsumerException;
|
||||
import jdk.jpackage.internal.cli.Validator.ValidatorException;
|
||||
@ -187,46 +188,97 @@ public class ValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_andThen() {
|
||||
|
||||
Function<String, Validator<String, Exception>> createFailingValidator = exceptionMessage -> {
|
||||
Objects.requireNonNull(exceptionMessage);
|
||||
var exceptionFactory = OptionValueExceptionFactory.build().ctor(TestException::new).messageFormatter((_, _) -> {
|
||||
return exceptionMessage;
|
||||
}).create();
|
||||
|
||||
return Validator.<String, Exception>build()
|
||||
.predicate(_ -> false)
|
||||
.formatString("")
|
||||
.exceptionFactory(exceptionFactory).create();
|
||||
};
|
||||
public void test_and() {
|
||||
|
||||
Function<Validator<String, ? extends Exception>, List<? extends Exception>> validate = validator -> {
|
||||
return validator.validate(OptionName.of("a"), ParsedValue.create("str", StringToken.of("str")));
|
||||
};
|
||||
|
||||
var pass = Validator.<String, RuntimeException>build().predicate(_ -> true).create();
|
||||
var pass = new RecordingValidator<>(Validator.<String, RuntimeException>build().predicate(_ -> true).create());
|
||||
|
||||
var foo = createFailingValidator.apply("foo");
|
||||
var bar = createFailingValidator.apply("bar");
|
||||
var buz = createFailingValidator.apply("buz");
|
||||
var foo = failingValidator("foo");
|
||||
var bar = failingValidator("bar");
|
||||
var buz = failingValidator("buz");
|
||||
|
||||
assertExceptionListEquals(List.of(
|
||||
new TestException("foo"),
|
||||
new TestException("bar"),
|
||||
new TestException("buz")
|
||||
), validate.apply(foo.andThen(bar).andThen(pass).andThen(buz)));
|
||||
), validate.apply(foo.and(bar).and(pass).and(buz)));
|
||||
assertEquals(1, pass.counter());
|
||||
|
||||
pass.resetCounter();
|
||||
assertExceptionListEquals(List.of(
|
||||
new TestException("bar"),
|
||||
new TestException("buz"),
|
||||
new TestException("foo")
|
||||
), validate.apply(pass.andThen(bar).andThen(buz).andThen(foo)));
|
||||
), validate.apply(pass.and(bar).and(buz).and(foo)));
|
||||
assertEquals(1, pass.counter());
|
||||
|
||||
assertExceptionListEquals(List.of(
|
||||
new TestException("foo"),
|
||||
new TestException("foo")
|
||||
), validate.apply(foo.andThen(foo)));
|
||||
), validate.apply(foo.and(foo)));
|
||||
|
||||
pass.resetCounter();
|
||||
assertExceptionListEquals(List.of(
|
||||
), validate.apply(pass.and(pass)));
|
||||
assertEquals(2, pass.counter());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_or() {
|
||||
|
||||
Function<Validator<String, ? extends Exception>, List<? extends Exception>> validate = validator -> {
|
||||
return validator.validate(OptionName.of("a"), ParsedValue.create("str", StringToken.of("str")));
|
||||
};
|
||||
|
||||
var pass = new RecordingValidator<>(Validator.<String, RuntimeException>build().predicate(_ -> true).create());
|
||||
|
||||
var foo = new RecordingValidator<>(failingValidator("foo"));
|
||||
var bar = new RecordingValidator<>(failingValidator("bar"));
|
||||
var buz = new RecordingValidator<>(failingValidator("buz"));
|
||||
|
||||
Runnable resetCounters = () -> {
|
||||
Stream.of(pass, foo, bar, buz).forEach(RecordingValidator::resetCounter);
|
||||
};
|
||||
|
||||
assertExceptionListEquals(List.of(
|
||||
new TestException("foo"),
|
||||
new TestException("bar"),
|
||||
new TestException("buz")
|
||||
), validate.apply(foo.or(bar).or(buz)));
|
||||
assertEquals(1, foo.counter());
|
||||
assertEquals(1, bar.counter());
|
||||
assertEquals(1, buz.counter());
|
||||
|
||||
resetCounters.run();
|
||||
assertExceptionListEquals(List.of(
|
||||
), validate.apply(foo.or(bar).or(pass).or(buz)));
|
||||
assertEquals(1, foo.counter());
|
||||
assertEquals(1, bar.counter());
|
||||
assertEquals(1, pass.counter());
|
||||
assertEquals(0, buz.counter());
|
||||
|
||||
resetCounters.run();
|
||||
assertExceptionListEquals(List.of(
|
||||
), validate.apply(pass.or(bar).or(buz).or(foo)));
|
||||
assertEquals(1, pass.counter());
|
||||
assertEquals(0, bar.counter());
|
||||
assertEquals(0, buz.counter());
|
||||
assertEquals(0, foo.counter());
|
||||
|
||||
resetCounters.run();
|
||||
assertExceptionListEquals(List.of(
|
||||
new TestException("foo"),
|
||||
new TestException("foo")
|
||||
), validate.apply(foo.or(foo)));
|
||||
assertEquals(2, foo.counter());
|
||||
|
||||
resetCounters.run();
|
||||
assertExceptionListEquals(List.of(
|
||||
), validate.apply(pass.or(pass)));
|
||||
assertEquals(1, pass.counter());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ -269,6 +321,17 @@ public class ValidatorTest {
|
||||
return data;
|
||||
}
|
||||
|
||||
private static Validator<String, Exception> failingValidator(String exceptionMessage) {
|
||||
var exceptionFactory = OptionValueExceptionFactory.build().ctor(TestException::new).messageFormatter((_, _) -> {
|
||||
return exceptionMessage;
|
||||
}).create();
|
||||
|
||||
return Validator.<String, Exception>build()
|
||||
.predicate(_ -> false)
|
||||
.formatString("")
|
||||
.exceptionFactory(exceptionFactory).create();
|
||||
}
|
||||
|
||||
|
||||
static final class FooException extends Exception {
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2026, 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
|
||||
@ -49,11 +49,10 @@ public class ArgumentsFilteringTest {
|
||||
public void test1() {
|
||||
JPackageCommand cmd = JPackageCommand.helloAppImage();
|
||||
cmd.executeAndAssertHelloAppImageCreated();
|
||||
var appVerifier = HelloApp.assertMainLauncher(cmd);
|
||||
if (appVerifier != null) {
|
||||
HelloApp.assertMainLauncher(cmd).ifPresent(appVerifier -> {
|
||||
appVerifier.execute("-psn_1_1");
|
||||
appVerifier.verifyOutput();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -61,10 +60,9 @@ public class ArgumentsFilteringTest {
|
||||
JPackageCommand cmd = JPackageCommand.helloAppImage()
|
||||
.addArguments("--arguments", "-psn_2_2");
|
||||
cmd.executeAndAssertHelloAppImageCreated();
|
||||
var appVerifier = HelloApp.assertMainLauncher(cmd);
|
||||
if (appVerifier != null) {
|
||||
HelloApp.assertMainLauncher(cmd).ifPresent(appVerifier -> {
|
||||
appVerifier.execute("-psn_1_1");
|
||||
appVerifier.verifyOutput("-psn_2_2");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2026, 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
|
||||
@ -26,6 +26,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Predicate;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
import jdk.jpackage.internal.util.XmlUtils;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
@ -133,8 +134,7 @@ public class AppImagePackageTest {
|
||||
*/
|
||||
@Test
|
||||
public static void testBadAppImage() throws IOException {
|
||||
Path appImageDir = TKit.createTempDirectory("appimage");
|
||||
Files.createFile(appImageDir.resolve("foo"));
|
||||
Path appImageDir = createInvalidAppImage();
|
||||
configureBadAppImage(appImageDir).addInitializer(cmd -> {
|
||||
cmd.removeArgumentWithValue("--name");
|
||||
}).run(Action.CREATE);
|
||||
@ -145,8 +145,7 @@ public class AppImagePackageTest {
|
||||
*/
|
||||
@Test
|
||||
public static void testBadAppImage2() throws IOException {
|
||||
Path appImageDir = TKit.createTempDirectory("appimage");
|
||||
Files.createFile(appImageDir.resolve("foo"));
|
||||
Path appImageDir = createInvalidAppImage();
|
||||
configureBadAppImage(appImageDir).run(Action.CREATE);
|
||||
}
|
||||
|
||||
@ -227,4 +226,19 @@ public class AppImagePackageTest {
|
||||
+ TKit.ICON_SUFFIX));
|
||||
}
|
||||
|
||||
private static Path createInvalidAppImage() throws IOException {
|
||||
Path appImageDir = TKit.createTempDirectory("appimage");
|
||||
if (TKit.isOSX()) {
|
||||
// Create minimal macOS bundle to prevent jpackage bail out early
|
||||
// with "error.parameter-not-mac-bundle" error.
|
||||
var bundle = new MacBundle(appImageDir);
|
||||
Files.createDirectories(bundle.macOsDir());
|
||||
Files.createFile(bundle.infoPlistFile());
|
||||
} else {
|
||||
Files.createFile(appImageDir.resolve("foo"));
|
||||
}
|
||||
|
||||
return appImageDir;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -643,7 +643,12 @@ public final class ErrorTest {
|
||||
.error("message.invalid-identifier", "#1"),
|
||||
// Bundle for mac app store should not have runtime commands
|
||||
testSpec().nativeType().addArgs("--mac-app-store", "--jlink-options", "--bind-services")
|
||||
.error("ERR_MissingJLinkOptMacAppStore", "--strip-native-commands")
|
||||
.error("ERR_MissingJLinkOptMacAppStore", "--strip-native-commands"),
|
||||
// Predefined app image must be a valid macOS bundle.
|
||||
testSpec().noAppDesc().nativeType().addArgs("--app-image", Token.EMPTY_DIR.token())
|
||||
.error("error.parameter-not-mac-bundle", JPackageCommand.cannedArgument(cmd -> {
|
||||
return Path.of(cmd.getArgumentValue("--app-image"));
|
||||
}, Token.EMPTY_DIR.token()), "--app-image")
|
||||
).map(TestSpec.Builder::create).toList());
|
||||
|
||||
macInvalidRuntime(testCases::add);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2026, 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
|
||||
@ -240,8 +240,7 @@ public final class MainClassTest {
|
||||
cmd.executeAndAssertHelloAppImageCreated();
|
||||
} else {
|
||||
cmd.executeAndAssertImageCreated();
|
||||
var appVerifier = HelloApp.assertMainLauncher(cmd);
|
||||
if (appVerifier != null) {
|
||||
HelloApp.assertMainLauncher(cmd).ifPresent(appVerifier -> {
|
||||
List<String> output = appVerifier
|
||||
.saveOutput(true)
|
||||
.expectedExitCode(1)
|
||||
@ -249,7 +248,7 @@ public final class MainClassTest {
|
||||
TKit.assertTextStream(String.format(
|
||||
"Error: Could not find or load main class %s",
|
||||
nonExistingMainClass)).apply(output);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CfgFile cfg = cmd.readLauncherCfgFile();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, 2026, 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
|
||||
@ -21,12 +21,14 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import static jdk.jpackage.test.WindowsHelper.killAppLauncherProcess;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.HelloApp;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.TKit;
|
||||
|
||||
/**
|
||||
* Test that terminating of the parent app launcher process automatically
|
||||
@ -46,7 +48,7 @@ import jdk.jpackage.test.JPackageCommand;
|
||||
public class Win8301247Test {
|
||||
|
||||
@Test
|
||||
public void test() throws InterruptedException {
|
||||
public void test() throws InterruptedException, ExecutionException {
|
||||
var cmd = JPackageCommand.helloAppImage().ignoreFakeRuntime();
|
||||
|
||||
// Launch the app in a way it doesn't exit to let us trap app laucnher
|
||||
@ -54,20 +56,41 @@ public class Win8301247Test {
|
||||
cmd.addArguments("--java-options", "-Djpackage.test.noexit=true");
|
||||
cmd.executeAndAssertImageCreated();
|
||||
|
||||
var f = new CompletableFuture<Process>();
|
||||
|
||||
// Launch the app in a separate thread
|
||||
new Thread(() -> {
|
||||
HelloApp.executeLauncher(cmd);
|
||||
HelloApp.assertMainLauncher(cmd).get().processListener(f::complete).execute();
|
||||
}).start();
|
||||
|
||||
// Wait a bit to let the app start
|
||||
Thread.sleep(Duration.ofSeconds(10));
|
||||
var mainLauncherProcess = f.get();
|
||||
|
||||
// Find the main app launcher process and kill it
|
||||
killAppLauncherProcess(cmd, null, 2);
|
||||
Optional<ProcessHandle> childProcess = Optional.empty();
|
||||
|
||||
// Wait a bit and check if child app launcher process is still running (it must NOT)
|
||||
Thread.sleep(Duration.ofSeconds(5));
|
||||
try {
|
||||
// Wait a bit to let the app start
|
||||
Thread.sleep(Duration.ofSeconds(10));
|
||||
|
||||
killAppLauncherProcess(cmd, null, 0);
|
||||
try (var children = mainLauncherProcess.children()) {
|
||||
childProcess = children.filter(p -> {
|
||||
return mainLauncherProcess.info().command().equals(p.info().command());
|
||||
}).findFirst();
|
||||
}
|
||||
|
||||
TKit.assertTrue(childProcess.isPresent(),
|
||||
String.format("Check the main launcher process with PID=%d restarted", mainLauncherProcess.pid()));
|
||||
} finally {
|
||||
// Kill the main app launcher process
|
||||
TKit.trace("About to kill the main launcher process...");
|
||||
mainLauncherProcess.destroyForcibly();
|
||||
|
||||
// Wait a bit and check if child app launcher process is still running (it must NOT)
|
||||
Thread.sleep(Duration.ofSeconds(5));
|
||||
|
||||
childProcess.ifPresent(p -> {
|
||||
TKit.assertTrue(!p.isAlive(), String.format(
|
||||
"Check restarted main launcher process with PID=%d is not alive", p.pid()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, 2026, 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
|
||||
@ -44,7 +44,6 @@ import static jdk.jpackage.test.HelloApp.configureAndExecute;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.Executor;
|
||||
import jdk.jpackage.test.TKit;
|
||||
import static jdk.jpackage.test.WindowsHelper.killProcess;
|
||||
|
||||
public class WinChildProcessTest {
|
||||
private static final Path TEST_APP_JAVA = TKit.TEST_SRC_ROOT
|
||||
@ -52,7 +51,7 @@ public class WinChildProcessTest {
|
||||
|
||||
@Test
|
||||
public static void test() {
|
||||
long childPid = 0;
|
||||
Optional<ProcessHandle> child = Optional.empty();
|
||||
try {
|
||||
JPackageCommand cmd = JPackageCommand
|
||||
.helloAppImage(TEST_APP_JAVA + "*Hello")
|
||||
@ -69,21 +68,18 @@ public class WinChildProcessTest {
|
||||
String pidStr = output.get(0);
|
||||
|
||||
// parse child PID
|
||||
childPid = Long.parseLong(pidStr.split("=", 2)[1]);
|
||||
var childPid = Long.parseLong(pidStr.split("=", 2)[1]);
|
||||
|
||||
// Check whether the termination of third party application launcher
|
||||
// also terminating the launched third party application
|
||||
// If third party application is not terminated the test is
|
||||
// successful else failure
|
||||
Optional<ProcessHandle> processHandle = ProcessHandle.of(childPid);
|
||||
boolean isAlive = processHandle.isPresent()
|
||||
&& processHandle.get().isAlive();
|
||||
TKit.assertTrue(isAlive, "Check child process is alive");
|
||||
child = ProcessHandle.of(childPid);
|
||||
boolean isAlive = child.map(ProcessHandle::isAlive).orElse(false);
|
||||
TKit.assertTrue(isAlive, String.format("Check child process with PID=%d is alive", childPid));
|
||||
} finally {
|
||||
if (childPid != 0) {
|
||||
// Kill only a specific child instance
|
||||
killProcess(childPid);
|
||||
}
|
||||
TKit.trace("About to kill the child process...");
|
||||
child.ifPresent(ProcessHandle::destroyForcibly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, 2026, 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
|
||||
@ -21,15 +21,18 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import static jdk.jpackage.test.WindowsHelper.killAppLauncherProcess;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.CfgFile;
|
||||
import jdk.jpackage.test.HelloApp;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.TKit;
|
||||
|
||||
/* @test
|
||||
* @bug 8340311
|
||||
@ -47,7 +50,7 @@ import jdk.jpackage.test.JPackageCommand;
|
||||
public class WinNoRestartTest {
|
||||
|
||||
@Test
|
||||
public static void test() throws InterruptedException, IOException {
|
||||
public static void test() throws InterruptedException, IOException, ExecutionException {
|
||||
var cmd = JPackageCommand.helloAppImage().ignoreFakeRuntime();
|
||||
|
||||
// Configure test app to launch in a way it will not exit
|
||||
@ -77,7 +80,7 @@ public class WinNoRestartTest {
|
||||
private static record NoRerunConfig(NoRerunSectionConfig firstSection,
|
||||
NoRerunSectionConfig secondSection, boolean expectedNoRestarted) {
|
||||
|
||||
void apply(JPackageCommand cmd, CfgFile origCfgFile) throws InterruptedException {
|
||||
void apply(JPackageCommand cmd, CfgFile origCfgFile) throws InterruptedException, ExecutionException {
|
||||
// Alter the main launcher .cfg file
|
||||
var cfgFile = new CfgFile();
|
||||
if (firstSection != null) {
|
||||
@ -92,16 +95,40 @@ public class WinNoRestartTest {
|
||||
// Save updated main launcher .cfg file
|
||||
cfgFile.save(cmd.appLauncherCfgPath(null));
|
||||
|
||||
var f = new CompletableFuture<Process>();
|
||||
|
||||
// Launch the app in a separate thread
|
||||
new Thread(() -> {
|
||||
HelloApp.executeLauncher(cmd);
|
||||
HelloApp.assertMainLauncher(cmd).get().processListener(f::complete).execute();
|
||||
}).start();
|
||||
|
||||
// Wait a bit to let the app start
|
||||
Thread.sleep(Duration.ofSeconds(10));
|
||||
var mainLauncherProcess = f.get();
|
||||
|
||||
// Find the main app launcher process and kill it
|
||||
killAppLauncherProcess(cmd, null, expectedNoRestarted ? 1 : 2);
|
||||
try {
|
||||
// Wait a bit to let the app start
|
||||
Thread.sleep(Duration.ofSeconds(10));
|
||||
|
||||
try (var children = mainLauncherProcess.children()) {
|
||||
Optional<String> childPid = children.filter(p -> {
|
||||
return mainLauncherProcess.info().command().equals(p.info().command());
|
||||
}).map(ProcessHandle::pid).map(Object::toString).findFirst();
|
||||
|
||||
Optional<String> expectedChildPid;
|
||||
if (expectedNoRestarted) {
|
||||
expectedChildPid = Optional.empty();
|
||||
} else {
|
||||
expectedChildPid = childPid.or(() -> {
|
||||
return Optional.of("<some>");
|
||||
});
|
||||
}
|
||||
TKit.assertEquals(expectedChildPid, childPid, String.format(
|
||||
"Check the main launcher process with PID=%d restarted",
|
||||
mainLauncherProcess.pid()));
|
||||
}
|
||||
} finally {
|
||||
TKit.trace("About to kill the main launcher process...");
|
||||
mainLauncherProcess.destroyForcibly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2013, 2026, 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
|
||||
@ -44,7 +44,8 @@ public class TestNewCastArray {
|
||||
// 'b' tests fail with only even numbers of annotations (8005681).
|
||||
String[] testclasses = {"Test1",
|
||||
"Test2a", "Test3a", "Test4a", "Test5a",
|
||||
"Test2b", "Test3b", "Test4b", "Test5b"
|
||||
"Test2b", "Test3b", "Test4b", "Test5b",
|
||||
"Test6a"
|
||||
};
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
@ -182,6 +183,11 @@ public class TestNewCastArray {
|
||||
case "ci2": expected = 0; break;
|
||||
case "ci22": expected = 0; break;
|
||||
|
||||
case "Test6a<init>": cexpected=4; break;
|
||||
case "test6aPrimitiveArray": expected = 0; break;
|
||||
case "test6aRefArray": expected = 0; break;
|
||||
case "test6aMethod": cexpected = 4; break;
|
||||
|
||||
default: expected = 0; break;
|
||||
}
|
||||
if(codeattr)
|
||||
@ -353,6 +359,18 @@ public class TestNewCastArray {
|
||||
Integer ci22 = (@A @A @B @B Integer)o; // FAIL expect 3, got 1
|
||||
}
|
||||
|
||||
static class Test6a {
|
||||
Test6a(){}
|
||||
long l = 0;
|
||||
// Cast expressions inside new array dimensions:
|
||||
int[] test6aPrimitiveArray = new int[(@A @A @B @B int) l];
|
||||
Integer[] test6aRefArray = new Integer[(@A @A @B @B int) l];
|
||||
private void test6aMethod() {
|
||||
int[] primitiveArray = new int[(@A @A @B @B int) l];
|
||||
Integer[] refArray = new Integer[(@A @A @B @B int) l];
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(RUNTIME) @Target({TYPE_USE}) @Repeatable( AC.class ) @interface A { }
|
||||
@Retention(RUNTIME) @Target({TYPE_USE}) @Repeatable( BC.class ) @interface B { }
|
||||
@Retention(RUNTIME) @Target({FIELD}) @Repeatable( FC.class ) @interface F { }
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 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.
|
||||
*/
|
||||
package org.openjdk.bench.java.lang.foreign;
|
||||
|
||||
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Param;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.lang.foreign.Arena;
|
||||
import java.lang.foreign.MemorySegment;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
|
||||
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
|
||||
@State(Scope.Benchmark)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@Fork(value = 3)
|
||||
public class FromJavaStringTest {
|
||||
|
||||
private String str;
|
||||
private MemorySegment strSegment;
|
||||
private int lengthBytes;
|
||||
|
||||
@Param({"5", "20", "100", "200", "451"})
|
||||
int size;
|
||||
|
||||
@Setup
|
||||
public void setup() {
|
||||
var arena = Arena.ofAuto();
|
||||
while (LOREM.length() < size) {
|
||||
LOREM += LOREM;
|
||||
}
|
||||
str = LOREM.substring(0, size);
|
||||
strSegment = arena.allocateFrom(str);
|
||||
lengthBytes = str.getBytes(UTF_8).length;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void segment_setString() {
|
||||
strSegment.setString(0, str, UTF_8);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void segment_copyStringRaw() {
|
||||
MemorySegment.copy(str, UTF_8, 0, strSegment, 0, str.length());
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void segment_copyStringBytes() {
|
||||
byte[] bytes = str.getBytes(UTF_8);
|
||||
MemorySegment.copy(bytes, 0, strSegment, JAVA_BYTE, 0, bytes.length);
|
||||
}
|
||||
|
||||
static String LOREM =
|
||||
"""
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
|
||||
ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
|
||||
mollit anim id est laborum.
|
||||
""";
|
||||
}
|
||||
@ -22,6 +22,9 @@
|
||||
*/
|
||||
package org.openjdk.bench.java.lang.foreign;
|
||||
|
||||
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
@ -47,6 +50,7 @@ import java.util.concurrent.TimeUnit;
|
||||
public class ToJavaStringTest {
|
||||
|
||||
private MemorySegment strSegment;
|
||||
private int length;
|
||||
|
||||
@Param({"5", "20", "100", "200", "451"})
|
||||
int size;
|
||||
@ -61,19 +65,33 @@ public class ToJavaStringTest {
|
||||
while (LOREM.length() < size) {
|
||||
LOREM += LOREM;
|
||||
}
|
||||
strSegment = arena.allocateFrom(LOREM.substring(0, size));
|
||||
var s = LOREM.substring(0, size);
|
||||
strSegment = arena.allocateFrom(s);
|
||||
length = s.getBytes(UTF_8).length;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public String panama_readString() {
|
||||
public String segment_getString() {
|
||||
return strSegment.getString(0);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public String segment_getStringLength() {
|
||||
return strSegment.getString(0, UTF_8, length);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public String jni_readString() {
|
||||
return readString(strSegment.address());
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public String segment_copyStringBytes() {
|
||||
byte[] bytes = new byte[length];
|
||||
MemorySegment.copy(strSegment, JAVA_BYTE, 0, bytes, 0, length);
|
||||
return new String(bytes, UTF_8);
|
||||
}
|
||||
|
||||
static native String readString(long addr);
|
||||
|
||||
static String LOREM = """
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user