306 lines
11 KiB
C++
306 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2012 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#ifndef ART_RUNTIME_SCOPED_THREAD_STATE_CHANGE_H_
|
|
#define ART_RUNTIME_SCOPED_THREAD_STATE_CHANGE_H_
|
|
|
|
#include "base/casts.h"
|
|
#include "java_vm_ext.h"
|
|
#include "jni_env_ext-inl.h"
|
|
#include "art_field.h"
|
|
#include "read_barrier.h"
|
|
#include "thread-inl.h"
|
|
#include "verify_object.h"
|
|
|
|
namespace art {
|
|
|
|
// Scoped change into and out of a particular state. Handles Runnable transitions that require
|
|
// more complicated suspension checking. The subclasses ScopedObjectAccessUnchecked and
|
|
// ScopedObjectAccess are used to handle the change into Runnable to Get direct access to objects,
|
|
// the unchecked variant doesn't aid annotalysis.
|
|
class ScopedThreadStateChange : public ValueObject {
|
|
public:
|
|
ScopedThreadStateChange(Thread* self, ThreadState new_thread_state)
|
|
REQUIRES(!Locks::thread_suspend_count_lock_) ALWAYS_INLINE
|
|
: self_(self), thread_state_(new_thread_state), expected_has_no_thread_(false) {
|
|
if (UNLIKELY(self_ == nullptr)) {
|
|
// Value chosen arbitrarily and won't be used in the destructor since thread_ == null.
|
|
old_thread_state_ = kTerminated;
|
|
Runtime* runtime = Runtime::Current();
|
|
CHECK(runtime == nullptr || !runtime->IsStarted() || runtime->IsShuttingDown(self_));
|
|
} else {
|
|
DCHECK_EQ(self, Thread::Current());
|
|
// Read state without locks, ok as state is effectively thread local and we're not interested
|
|
// in the suspend count (this will be handled in the runnable transitions).
|
|
old_thread_state_ = self->GetState();
|
|
if (old_thread_state_ != new_thread_state) {
|
|
if (new_thread_state == kRunnable) {
|
|
self_->TransitionFromSuspendedToRunnable();
|
|
} else if (old_thread_state_ == kRunnable) {
|
|
self_->TransitionFromRunnableToSuspended(new_thread_state);
|
|
} else {
|
|
// A suspended transition to another effectively suspended transition, ok to use Unsafe.
|
|
self_->SetState(new_thread_state);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
~ScopedThreadStateChange() REQUIRES(!Locks::thread_suspend_count_lock_) ALWAYS_INLINE {
|
|
if (UNLIKELY(self_ == nullptr)) {
|
|
if (!expected_has_no_thread_) {
|
|
Runtime* runtime = Runtime::Current();
|
|
bool shutting_down = (runtime == nullptr) || runtime->IsShuttingDown(nullptr);
|
|
CHECK(shutting_down);
|
|
}
|
|
} else {
|
|
if (old_thread_state_ != thread_state_) {
|
|
if (old_thread_state_ == kRunnable) {
|
|
self_->TransitionFromSuspendedToRunnable();
|
|
} else if (thread_state_ == kRunnable) {
|
|
self_->TransitionFromRunnableToSuspended(old_thread_state_);
|
|
} else {
|
|
// A suspended transition to another effectively suspended transition, ok to use Unsafe.
|
|
self_->SetState(old_thread_state_);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Thread* Self() const {
|
|
return self_;
|
|
}
|
|
|
|
protected:
|
|
// Constructor used by ScopedJniThreadState for an unattached thread that has access to the VM*.
|
|
ScopedThreadStateChange()
|
|
: self_(nullptr), thread_state_(kTerminated), old_thread_state_(kTerminated),
|
|
expected_has_no_thread_(true) {}
|
|
|
|
Thread* const self_;
|
|
const ThreadState thread_state_;
|
|
|
|
private:
|
|
ThreadState old_thread_state_;
|
|
const bool expected_has_no_thread_;
|
|
|
|
friend class ScopedObjectAccessUnchecked;
|
|
DISALLOW_COPY_AND_ASSIGN(ScopedThreadStateChange);
|
|
};
|
|
|
|
// Assumes we are already runnable.
|
|
class ScopedObjectAccessAlreadyRunnable : public ValueObject {
|
|
public:
|
|
Thread* Self() const {
|
|
return self_;
|
|
}
|
|
|
|
JNIEnvExt* Env() const {
|
|
return env_;
|
|
}
|
|
|
|
JavaVMExt* Vm() const {
|
|
return vm_;
|
|
}
|
|
|
|
bool ForceCopy() const {
|
|
return vm_->ForceCopy();
|
|
}
|
|
|
|
/*
|
|
* Add a local reference for an object to the indirect reference table associated with the
|
|
* current stack frame. When the native function returns, the reference will be discarded.
|
|
*
|
|
* We need to allow the same reference to be added multiple times, and cope with nullptr.
|
|
*
|
|
* This will be called on otherwise unreferenced objects. We cannot do GC allocations here, and
|
|
* it's best if we don't grab a mutex.
|
|
*/
|
|
template<typename T>
|
|
T AddLocalReference(mirror::Object* obj) const SHARED_REQUIRES(Locks::mutator_lock_) {
|
|
Locks::mutator_lock_->AssertSharedHeld(Self());
|
|
DCHECK(IsRunnable()); // Don't work with raw objects in non-runnable states.
|
|
DCHECK_NE(obj, Runtime::Current()->GetClearedJniWeakGlobal());
|
|
return obj == nullptr ? nullptr : Env()->AddLocalReference<T>(obj);
|
|
}
|
|
|
|
template<typename T>
|
|
T Decode(jobject obj) const
|
|
SHARED_REQUIRES(Locks::mutator_lock_) {
|
|
Locks::mutator_lock_->AssertSharedHeld(Self());
|
|
DCHECK(IsRunnable()); // Don't work with raw objects in non-runnable states.
|
|
return down_cast<T>(Self()->DecodeJObject(obj));
|
|
}
|
|
|
|
ArtField* DecodeField(jfieldID fid) const
|
|
SHARED_REQUIRES(Locks::mutator_lock_) {
|
|
Locks::mutator_lock_->AssertSharedHeld(Self());
|
|
DCHECK(IsRunnable()); // Don't work with raw objects in non-runnable states.
|
|
return reinterpret_cast<ArtField*>(fid);
|
|
}
|
|
|
|
jfieldID EncodeField(ArtField* field) const SHARED_REQUIRES(Locks::mutator_lock_) {
|
|
Locks::mutator_lock_->AssertSharedHeld(Self());
|
|
DCHECK(IsRunnable()); // Don't work with raw objects in non-runnable states.
|
|
return reinterpret_cast<jfieldID>(field);
|
|
}
|
|
|
|
ArtMethod* DecodeMethod(jmethodID mid) const SHARED_REQUIRES(Locks::mutator_lock_) {
|
|
Locks::mutator_lock_->AssertSharedHeld(Self());
|
|
DCHECK(IsRunnable()); // Don't work with raw objects in non-runnable states.
|
|
return reinterpret_cast<ArtMethod*>(mid);
|
|
}
|
|
|
|
jmethodID EncodeMethod(ArtMethod* method) const SHARED_REQUIRES(Locks::mutator_lock_) {
|
|
Locks::mutator_lock_->AssertSharedHeld(Self());
|
|
DCHECK(IsRunnable()); // Don't work with raw objects in non-runnable states.
|
|
return reinterpret_cast<jmethodID>(method);
|
|
}
|
|
|
|
bool IsRunnable() const {
|
|
return self_->GetState() == kRunnable;
|
|
}
|
|
|
|
protected:
|
|
explicit ScopedObjectAccessAlreadyRunnable(JNIEnv* env)
|
|
REQUIRES(!Locks::thread_suspend_count_lock_) ALWAYS_INLINE
|
|
: self_(ThreadForEnv(env)), env_(down_cast<JNIEnvExt*>(env)), vm_(env_->vm) {
|
|
}
|
|
|
|
explicit ScopedObjectAccessAlreadyRunnable(Thread* self)
|
|
REQUIRES(!Locks::thread_suspend_count_lock_) ALWAYS_INLINE
|
|
: self_(self), env_(down_cast<JNIEnvExt*>(self->GetJniEnv())),
|
|
vm_(env_ != nullptr ? env_->vm : nullptr) {
|
|
}
|
|
|
|
// Used when we want a scoped JNI thread state but have no thread/JNIEnv. Consequently doesn't
|
|
// change into Runnable or acquire a share on the mutator_lock_.
|
|
explicit ScopedObjectAccessAlreadyRunnable(JavaVM* vm)
|
|
: self_(nullptr), env_(nullptr), vm_(down_cast<JavaVMExt*>(vm)) {}
|
|
|
|
// Here purely to force inlining.
|
|
~ScopedObjectAccessAlreadyRunnable() ALWAYS_INLINE {
|
|
}
|
|
|
|
// Self thread, can be null.
|
|
Thread* const self_;
|
|
// The full JNIEnv.
|
|
JNIEnvExt* const env_;
|
|
// The full JavaVM.
|
|
JavaVMExt* const vm_;
|
|
};
|
|
|
|
// Entry/exit processing for transitions from Native to Runnable (ie within JNI functions).
|
|
//
|
|
// This class performs the necessary thread state switching to and from Runnable and lets us
|
|
// amortize the cost of working out the current thread. Additionally it lets us check (and repair)
|
|
// apps that are using a JNIEnv on the wrong thread. The class also decodes and encodes Objects
|
|
// into jobjects via methods of this class. Performing this here enforces the Runnable thread state
|
|
// for use of Object, thereby inhibiting the Object being modified by GC whilst native or VM code
|
|
// is also manipulating the Object.
|
|
//
|
|
// The destructor transitions back to the previous thread state, typically Native. In this state
|
|
// GC and thread suspension may occur.
|
|
//
|
|
// For annotalysis the subclass ScopedObjectAccess (below) makes it explicit that a shared of
|
|
// the mutator_lock_ will be acquired on construction.
|
|
class ScopedObjectAccessUnchecked : public ScopedObjectAccessAlreadyRunnable {
|
|
public:
|
|
explicit ScopedObjectAccessUnchecked(JNIEnv* env)
|
|
REQUIRES(!Locks::thread_suspend_count_lock_) ALWAYS_INLINE
|
|
: ScopedObjectAccessAlreadyRunnable(env), tsc_(Self(), kRunnable) {
|
|
Self()->VerifyStack();
|
|
Locks::mutator_lock_->AssertSharedHeld(Self());
|
|
}
|
|
|
|
explicit ScopedObjectAccessUnchecked(Thread* self)
|
|
REQUIRES(!Locks::thread_suspend_count_lock_) ALWAYS_INLINE
|
|
: ScopedObjectAccessAlreadyRunnable(self), tsc_(self, kRunnable) {
|
|
Self()->VerifyStack();
|
|
Locks::mutator_lock_->AssertSharedHeld(Self());
|
|
}
|
|
|
|
// Used when we want a scoped JNI thread state but have no thread/JNIEnv. Consequently doesn't
|
|
// change into Runnable or acquire a share on the mutator_lock_.
|
|
explicit ScopedObjectAccessUnchecked(JavaVM* vm) ALWAYS_INLINE
|
|
: ScopedObjectAccessAlreadyRunnable(vm), tsc_() {}
|
|
|
|
private:
|
|
// The scoped thread state change makes sure that we are runnable and restores the thread state
|
|
// in the destructor.
|
|
const ScopedThreadStateChange tsc_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ScopedObjectAccessUnchecked);
|
|
};
|
|
|
|
// Annotalysis helping variant of the above.
|
|
class ScopedObjectAccess : public ScopedObjectAccessUnchecked {
|
|
public:
|
|
explicit ScopedObjectAccess(JNIEnv* env)
|
|
REQUIRES(!Locks::thread_suspend_count_lock_)
|
|
SHARED_LOCK_FUNCTION(Locks::mutator_lock_) ALWAYS_INLINE
|
|
: ScopedObjectAccessUnchecked(env) {
|
|
}
|
|
|
|
explicit ScopedObjectAccess(Thread* self)
|
|
REQUIRES(!Locks::thread_suspend_count_lock_)
|
|
SHARED_LOCK_FUNCTION(Locks::mutator_lock_) ALWAYS_INLINE
|
|
: ScopedObjectAccessUnchecked(self) {
|
|
}
|
|
|
|
~ScopedObjectAccess() UNLOCK_FUNCTION(Locks::mutator_lock_) ALWAYS_INLINE {
|
|
// Base class will release share of lock. Invoked after this destructor.
|
|
}
|
|
|
|
private:
|
|
// TODO: remove this constructor. It is used by check JNI's ScopedCheck to make it believe that
|
|
// routines operating with just a VM are sound, they are not, but when you have just a VM
|
|
// you cannot call the unsound routines.
|
|
explicit ScopedObjectAccess(JavaVM* vm)
|
|
SHARED_LOCK_FUNCTION(Locks::mutator_lock_)
|
|
: ScopedObjectAccessUnchecked(vm) {}
|
|
|
|
friend class ScopedCheck;
|
|
DISALLOW_COPY_AND_ASSIGN(ScopedObjectAccess);
|
|
};
|
|
|
|
// Annotalysis helper for going to a suspended state from runnable.
|
|
class ScopedThreadSuspension : public ValueObject {
|
|
public:
|
|
explicit ScopedThreadSuspension(Thread* self, ThreadState suspended_state)
|
|
REQUIRES(!Locks::thread_suspend_count_lock_, !Roles::uninterruptible_)
|
|
UNLOCK_FUNCTION(Locks::mutator_lock_)
|
|
ALWAYS_INLINE
|
|
: self_(self), suspended_state_(suspended_state) {
|
|
DCHECK(self_ != nullptr);
|
|
self_->TransitionFromRunnableToSuspended(suspended_state);
|
|
}
|
|
|
|
~ScopedThreadSuspension() SHARED_LOCK_FUNCTION(Locks::mutator_lock_) ALWAYS_INLINE {
|
|
DCHECK_EQ(self_->GetState(), suspended_state_);
|
|
self_->TransitionFromSuspendedToRunnable();
|
|
}
|
|
|
|
private:
|
|
Thread* const self_;
|
|
const ThreadState suspended_state_;
|
|
DISALLOW_COPY_AND_ASSIGN(ScopedThreadSuspension);
|
|
};
|
|
|
|
|
|
} // namespace art
|
|
|
|
#endif // ART_RUNTIME_SCOPED_THREAD_STATE_CHANGE_H_
|