Merge remote-tracking branch 'jdk/master' into accelerated-triggers

This commit is contained in:
Kelvin Nilsen 2026-01-13 14:55:35 +00:00
commit e772e4b84d
85 changed files with 1825 additions and 582 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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." },

View File

@ -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();

View File

@ -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 {};

View File

@ -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);
}

View File

@ -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);

View File

@ -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));
}

View File

@ -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) {

View File

@ -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.
}

View File

@ -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;

View File

@ -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) {

View File

@ -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) {

View File

@ -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();

View File

@ -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);
}

View File

@ -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;

View File

@ -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()) {

View File

@ -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

View File

@ -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();
}

View File

@ -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;

View File

@ -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));
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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 doesnt 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);
}
/**

View File

@ -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);
}
});
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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>

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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)

View File

@ -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

View File

@ -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);
}
}

View File

@ -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 &quot;the same&quot; 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
*/

View File

@ -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 &quot;the same&quot; 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

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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 {

View File

@ -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()));
}),

View File

@ -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;

View File

@ -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) {

View File

@ -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();

View File

@ -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 {

View File

@ -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();
};
}
}

View File

@ -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

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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>>();

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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();
}
}

View File

@ -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"},

View 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;
}
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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));

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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));

View File

@ -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) {

View File

@ -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");
}

View File

@ -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>",

View File

@ -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> {

View File

@ -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 {

View File

@ -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");
}
});
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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()));
});
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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 { }

View File

@ -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.
""";
}

View File

@ -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 = """