diff options
| -rw-r--r-- | runtime/hidden_api.h | 174 | ||||
| -rw-r--r-- | runtime/interpreter/unstarted_runtime.cc | 19 | ||||
| -rw-r--r-- | runtime/jni_internal.cc | 35 | ||||
| -rw-r--r-- | runtime/native/java_lang_Class.cc | 28 | ||||
| -rw-r--r-- | runtime/native/java_lang_reflect_Field.cc | 13 | ||||
| -rw-r--r-- | runtime/reflection.cc | 14 |
6 files changed, 114 insertions, 169 deletions
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h index de3a51a2ac..7e41c1dd3c 100644 --- a/runtime/hidden_api.h +++ b/runtime/hidden_api.h @@ -24,144 +24,104 @@ namespace art { namespace hiddenapi { -// Returns true if member with `access flags` should only be accessed from -// boot class path. -inline bool IsMemberHidden(uint32_t access_flags) { - if (!Runtime::Current()->AreHiddenApiChecksEnabled()) { - return false; - } - - switch (HiddenApiAccessFlags::DecodeFromRuntime(access_flags)) { - case HiddenApiAccessFlags::kWhitelist: - case HiddenApiAccessFlags::kLightGreylist: - case HiddenApiAccessFlags::kDarkGreylist: - return false; - case HiddenApiAccessFlags::kBlacklist: - return true; - } -} - -// Returns true if we should warn about non-boot class path accessing member -// with `access_flags`. -inline bool ShouldWarnAboutMember(uint32_t access_flags) { - if (!Runtime::Current()->AreHiddenApiChecksEnabled()) { - return false; - } +enum Action { + kAllow, + kAllowButWarn, + kDeny +}; +inline Action GetMemberAction(uint32_t access_flags) { switch (HiddenApiAccessFlags::DecodeFromRuntime(access_flags)) { case HiddenApiAccessFlags::kWhitelist: - return false; + return kAllow; case HiddenApiAccessFlags::kLightGreylist: case HiddenApiAccessFlags::kDarkGreylist: - return true; + return kAllowButWarn; case HiddenApiAccessFlags::kBlacklist: - // We should never access a blacklisted member from non-boot class path, - // but this function is called before we establish the origin of the access. - // Return false here, we do not want to warn when boot class path accesses - // a blacklisted member. - return false; + return kDeny; } } -// Returns true if caller `num_frames` up the stack is in boot class path. -inline bool IsCallerInBootClassPath(Thread* self, size_t num_frames) - REQUIRES_SHARED(Locks::mutator_lock_) { - ObjPtr<mirror::Class> klass = GetCallingClass(self, num_frames); - if (klass == nullptr) { - // Unattached native thread. Assume that this is *not* boot class path. - return false; - } - return klass->IsBootStrapClassLoaded(); -} - -// Returns true if `caller` should not be allowed to access member with `access_flags`. -inline bool ShouldBlockAccessToMember(uint32_t access_flags, mirror::Class* caller) - REQUIRES_SHARED(Locks::mutator_lock_) { - return IsMemberHidden(access_flags) && - !caller->IsBootStrapClassLoaded(); -} - -// Returns true if `caller` should not be allowed to access `member`. -template<typename T> -inline bool ShouldBlockAccessToMember(T* member, ArtMethod* caller) - REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(member != nullptr); - DCHECK(!caller->IsRuntimeMethod()); - return ShouldBlockAccessToMember(member->GetAccessFlags(), caller->GetDeclaringClass()); -} - -// Returns true if the caller `num_frames` up the stack should not be allowed -// to access `member`. -template<typename T> -inline bool ShouldBlockAccessToMember(T* member, Thread* self, size_t num_frames) - REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(member != nullptr); - return IsMemberHidden(member->GetAccessFlags()) && - !IsCallerInBootClassPath(self, num_frames); // This is expensive. Save it for last. -} - // Issue a warning about field access. inline void WarnAboutMemberAccess(ArtField* field) REQUIRES_SHARED(Locks::mutator_lock_) { - Runtime::Current()->SetPendingHiddenApiWarning(true); LOG(WARNING) << "Access to hidden field " << field->PrettyField(); } // Issue a warning about method access. inline void WarnAboutMemberAccess(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) { - Runtime::Current()->SetPendingHiddenApiWarning(true); LOG(WARNING) << "Access to hidden method " << method->PrettyMethod(); } -// Set access flags of `member` to be in hidden API whitelist. This can be disabled -// with a Runtime::SetDedupHiddenApiWarnings. -template<typename T> -inline void MaybeWhitelistMember(T* member) REQUIRES_SHARED(Locks::mutator_lock_) { - if (Runtime::Current()->ShouldDedupeHiddenApiWarnings()) { - member->SetAccessFlags(HiddenApiAccessFlags::EncodeForRuntime( - member->GetAccessFlags(), HiddenApiAccessFlags::kWhitelist)); - DCHECK(!ShouldWarnAboutMember(member->GetAccessFlags())); - } -} - -// Check if `caller` should be allowed to access `member` and warn if not. +// Returns true if access to `member` should be denied to the caller of the +// reflective query. The decision is based on whether the caller is in boot +// class path or not. Because different users of this function determine this +// in a different way, `fn_caller_in_boot(self)` is called and should return +// true if the caller is in boot class path. +// This function might print warnings into the log if the member is greylisted. template<typename T> -inline void MaybeWarnAboutMemberAccess(T* member, ArtMethod* caller) +inline bool ShouldBlockAccessToMember(T* member, + Thread* self, + std::function<bool(Thread*)> fn_caller_in_boot) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(member != nullptr); - DCHECK(!caller->IsRuntimeMethod()); - if (!Runtime::Current()->AreHiddenApiChecksEnabled() || - member == nullptr || - !ShouldWarnAboutMember(member->GetAccessFlags()) || - caller->GetDeclaringClass()->IsBootStrapClassLoaded()) { - return; + + if (!Runtime::Current()->AreHiddenApiChecksEnabled()) { + // Exit early. Nothing to enforce. + return false; + } + + Action action = GetMemberAction(member->GetAccessFlags()); + if (action == kAllow) { + // Nothing to do. + return false; } - WarnAboutMember(member); - MaybeWhitelistMember(member); + // Member is hidden. Walk the stack to find the caller. + // This can be *very* expensive. Save it for last. + if (fn_caller_in_boot(self)) { + // Caller in boot class path. Exit. + return false; + } + + // Member is hidden and we are not in the boot class path. Act accordingly. + if (action == kAllowButWarn) { + // Allow access to this member but print a warning. Depending on a runtime + // flag, we might move the member into whitelist and skip the warning the + // next time the member is used. + Runtime::Current()->SetPendingHiddenApiWarning(true); + if (Runtime::Current()->ShouldDedupeHiddenApiWarnings()) { + member->SetAccessFlags(HiddenApiAccessFlags::EncodeForRuntime( + member->GetAccessFlags(), HiddenApiAccessFlags::kWhitelist)); + } + WarnAboutMemberAccess(member); + return false; + } else { + DCHECK_EQ(action, hiddenapi::kDeny); + return true; + } } -// Check if the caller `num_frames` up the stack should be allowed to access -// `member` and warn if not. -template<typename T> -inline void MaybeWarnAboutMemberAccess(T* member, Thread* self, size_t num_frames) +// Returns true if access to member with `access_flags` should be denied to `caller`. +// This function should be called on statically linked uses of hidden API. +inline bool ShouldBlockAccessToMember(uint32_t access_flags, mirror::Class* caller) REQUIRES_SHARED(Locks::mutator_lock_) { - if (!Runtime::Current()->AreHiddenApiChecksEnabled() || - member == nullptr || - !ShouldWarnAboutMember(member->GetAccessFlags())) { - return; + if (!Runtime::Current()->AreHiddenApiChecksEnabled()) { + // Exit early. Nothing to enforce. + return false; + } + + // Only continue if we want to deny access. Warnings are *not* printed. + if (GetMemberAction(access_flags) != kDeny) { + return false; } - // Walk the stack to find the caller. This is *very* expensive. Save it for last. - ObjPtr<mirror::Class> klass = GetCallingClass(self, num_frames); - if (klass == nullptr) { - // Unattached native thread, assume that this is *not* boot class path - // and enforce the rules. - } else if (klass->IsBootStrapClassLoaded()) { - return; + // Member is hidden. Check if the caller is in boot class path. + if (caller == nullptr) { + // The caller is unknown. We assume that this is *not* boot class path. + return true; } - WarnAboutMemberAccess(member); - MaybeWhitelistMember(member); + return !caller->IsBootStrapClassLoaded(); } } // namespace hiddenapi diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc index b9b00519d1..85acc71377 100644 --- a/runtime/interpreter/unstarted_runtime.cc +++ b/runtime/interpreter/unstarted_runtime.cc @@ -176,6 +176,13 @@ static mirror::String* GetClassName(Thread* self, ShadowFrame* shadow_frame, siz return param->AsString(); } +template<typename T> +static ALWAYS_INLINE bool ShouldBlockAccessToMember(T* member, ShadowFrame* frame) + REQUIRES_SHARED(Locks::mutator_lock_) { + return hiddenapi::ShouldBlockAccessToMember( + member->GetAccessFlags(), frame->GetMethod()->GetDeclaringClass()); +} + void UnstartedRuntime::UnstartedClassForNameCommon(Thread* self, ShadowFrame* shadow_frame, JValue* result, @@ -267,8 +274,7 @@ void UnstartedRuntime::UnstartedClassNewInstance( auto* cl = Runtime::Current()->GetClassLinker(); if (cl->EnsureInitialized(self, h_klass, true, true)) { ArtMethod* cons = h_klass->FindConstructor("()V", cl->GetImagePointerSize()); - if (cons != nullptr && - hiddenapi::ShouldBlockAccessToMember(cons, shadow_frame->GetMethod())) { + if (cons != nullptr && ShouldBlockAccessToMember(cons, shadow_frame)) { cons = nullptr; } if (cons != nullptr) { @@ -313,8 +319,7 @@ void UnstartedRuntime::UnstartedClassGetDeclaredField( } } } - if (found != nullptr && - hiddenapi::ShouldBlockAccessToMember(found, shadow_frame->GetMethod())) { + if (found != nullptr && ShouldBlockAccessToMember(found, shadow_frame)) { found = nullptr; } if (found == nullptr) { @@ -379,8 +384,7 @@ void UnstartedRuntime::UnstartedClassGetDeclaredMethod( self, klass, name, args); } } - if (method != nullptr && - hiddenapi::ShouldBlockAccessToMember(method->GetArtMethod(), shadow_frame->GetMethod())) { + if (method != nullptr && ShouldBlockAccessToMember(method->GetArtMethod(), shadow_frame)) { method = nullptr; } result->SetL(method); @@ -418,8 +422,7 @@ void UnstartedRuntime::UnstartedClassGetDeclaredConstructor( } } if (constructor != nullptr && - hiddenapi::ShouldBlockAccessToMember( - constructor->GetArtMethod(), shadow_frame->GetMethod())) { + ShouldBlockAccessToMember(constructor->GetArtMethod(), shadow_frame)) { constructor = nullptr; } result->SetL(constructor); diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc index dc0d65dcc1..666fb98354 100644 --- a/runtime/jni_internal.cc +++ b/runtime/jni_internal.cc @@ -80,17 +80,28 @@ namespace art { // things not rendering correctly. E.g. b/16858794 static constexpr bool kWarnJniAbort = false; -// Helpers to check if we need to warn about accessing hidden API fields and to call instrumentation -// functions for them. These take jobjects so we don't need to set up handles for the rare case -// where these actually do something. Once these functions return it is possible there will be -// a pending exception if the instrumentation happens to throw one. +static bool IsCallerInBootClassPath(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { + ObjPtr<mirror::Class> klass = GetCallingClass(self, /* num_frames */ 1); + // If `klass` is null, it is an unattached native thread. Assume this is + // *not* boot class path. + return klass != nullptr && klass->IsBootStrapClassLoaded(); +} + +template<typename T> +ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_) { + return hiddenapi::ShouldBlockAccessToMember(member, self, IsCallerInBootClassPath); +} + +// Helpers to call instrumentation functions for fields. These take jobjects so we don't need to set +// up handles for the rare case where these actually do something. Once these functions return it is +// possible there will be a pending exception if the instrumentation happens to throw one. static void NotifySetObjectField(ArtField* field, jobject obj, jobject jval) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK_EQ(field->GetTypeAsPrimitiveType(), Primitive::kPrimNot); - Thread* self = Thread::Current(); - hiddenapi::MaybeWarnAboutMemberAccess(field, self, /* num_frames */ 1); instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); if (UNLIKELY(instrumentation->HasFieldWriteListeners())) { + Thread* self = Thread::Current(); ArtMethod* cur_method = self->GetCurrentMethod(/*dex_pc*/ nullptr, /*check_suspended*/ true, /*abort_on_error*/ false); @@ -115,10 +126,9 @@ static void NotifySetObjectField(ArtField* field, jobject obj, jobject jval) static void NotifySetPrimitiveField(ArtField* field, jobject obj, JValue val) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK_NE(field->GetTypeAsPrimitiveType(), Primitive::kPrimNot); - Thread* self = Thread::Current(); - hiddenapi::MaybeWarnAboutMemberAccess(field, self, /* num_frames */ 1); instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); if (UNLIKELY(instrumentation->HasFieldWriteListeners())) { + Thread* self = Thread::Current(); ArtMethod* cur_method = self->GetCurrentMethod(/*dex_pc*/ nullptr, /*check_suspended*/ true, /*abort_on_error*/ false); @@ -140,10 +150,9 @@ static void NotifySetPrimitiveField(ArtField* field, jobject obj, JValue val) static void NotifyGetField(ArtField* field, jobject obj) REQUIRES_SHARED(Locks::mutator_lock_) { - Thread* self = Thread::Current(); - hiddenapi::MaybeWarnAboutMemberAccess(field, self, /* num_frames */ 1); instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); if (UNLIKELY(instrumentation->HasFieldReadListeners())) { + Thread* self = Thread::Current(); ArtMethod* cur_method = self->GetCurrentMethod(/*dex_pc*/ nullptr, /*check_suspended*/ true, /*abort_on_error*/ false); @@ -243,8 +252,7 @@ static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class, } else { method = c->FindClassMethod(name, sig, pointer_size); } - if (method != nullptr && - hiddenapi::ShouldBlockAccessToMember(method, soa.Self(), /* num_frames */ 1)) { + if (method != nullptr && ShouldBlockAccessToMember(method, soa.Self())) { method = nullptr; } if (method == nullptr || method->IsStatic() != is_static) { @@ -323,8 +331,7 @@ static jfieldID FindFieldID(const ScopedObjectAccess& soa, jclass jni_class, con } else { field = c->FindInstanceField(name, field_type->GetDescriptor(&temp)); } - if (field != nullptr && - hiddenapi::ShouldBlockAccessToMember(field, soa.Self(), /* num_frames */ 1)) { + if (field != nullptr && ShouldBlockAccessToMember(field, soa.Self())) { field = nullptr; } if (field == nullptr) { diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc index a1f23bdfb8..2091a27ffd 100644 --- a/runtime/native/java_lang_Class.cc +++ b/runtime/native/java_lang_Class.cc @@ -48,12 +48,8 @@ namespace art { -ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - if (!Runtime::Current()->AreHiddenApiChecksEnabled()) { - return false; - } - +// Returns true if the first non-ClassClass caller up the stack is in boot class path. +static bool IsCallerInBootClassPath(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { // Walk the stack and find the first frame not from java.lang.Class. // This is very expensive. Save this till the last. struct FirstNonClassClassCallerVisitor : public StackVisitor { @@ -84,8 +80,16 @@ ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self) FirstNonClassClassCallerVisitor visitor(self); visitor.WalkStack(); - return visitor.caller == nullptr || - !visitor.caller->GetDeclaringClass()->IsBootStrapClassLoaded(); + return visitor.caller != nullptr && + visitor.caller->GetDeclaringClass()->IsBootStrapClassLoaded(); +} + +// Returns true if the first non-ClassClass caller up the stack is not allowed to +// access hidden APIs. This can be *very* expensive. Never call this in a loop. +ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_) { + return Runtime::Current()->AreHiddenApiChecksEnabled() && + !IsCallerInBootClassPath(self); } // Returns true if the first non-ClassClass caller up the stack should not be @@ -93,9 +97,7 @@ ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self) template<typename T> ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(member != nullptr); - return hiddenapi::IsMemberHidden(member->GetAccessFlags()) && - ShouldEnforceHiddenApi(self); + return hiddenapi::ShouldBlockAccessToMember(member, self, IsCallerInBootClassPath); } // Returns true if a class member should be discoverable with reflection given @@ -109,14 +111,13 @@ ALWAYS_INLINE static bool IsDiscoverable(bool public_only, return false; } - if (enforce_hidden_api && hiddenapi::IsMemberHidden(access_flags)) { + if (enforce_hidden_api && hiddenapi::GetMemberAction(access_flags) == hiddenapi::kDeny) { return false; } return true; } - ALWAYS_INLINE static inline ObjPtr<mirror::Class> DecodeClass( const ScopedFastNativeObjectAccess& soa, jobject java_class) REQUIRES_SHARED(Locks::mutator_lock_) { @@ -831,7 +832,6 @@ static jobject Class_newInstance(JNIEnv* env, jobject javaThis) { return nullptr; } } - hiddenapi::MaybeWarnAboutMemberAccess(constructor, soa.Self(), /* num_frames */ 1); // Invoke the constructor. JValue result; uint32_t args[1] = { static_cast<uint32_t>(reinterpret_cast<uintptr_t>(receiver.Get())) }; diff --git a/runtime/native/java_lang_reflect_Field.cc b/runtime/native/java_lang_reflect_Field.cc index db7f4bb18c..f990c0421d 100644 --- a/runtime/native/java_lang_reflect_Field.cc +++ b/runtime/native/java_lang_reflect_Field.cc @@ -25,7 +25,6 @@ #include "common_throws.h" #include "dex/dex_file-inl.h" #include "dex/dex_file_annotations.h" -#include "hidden_api.h" #include "jni_internal.h" #include "mirror/class-inl.h" #include "mirror/field-inl.h" @@ -162,9 +161,6 @@ static jobject Field_get(JNIEnv* env, jobject javaField, jobject javaObj) { DCHECK(soa.Self()->IsExceptionPending()); return nullptr; } - - hiddenapi::MaybeWarnAboutMemberAccess(f->GetArtField(), soa.Self(), /* num_frames */ 1); - // We now don't expect suspension unless an exception is thrown. // Get the field's value, boxing if necessary. Primitive::Type field_type = f->GetTypeAsPrimitiveType(); @@ -187,14 +183,13 @@ ALWAYS_INLINE inline static JValue GetPrimitiveField(JNIEnv* env, DCHECK(soa.Self()->IsExceptionPending()); return JValue(); } + // If field is not set to be accessible, verify it can be accessed by the caller. if (!f->IsAccessible() && !VerifyFieldAccess<false>(soa.Self(), f, o)) { DCHECK(soa.Self()->IsExceptionPending()); return JValue(); } - hiddenapi::MaybeWarnAboutMemberAccess(f->GetArtField(), soa.Self(), /* num_frames */ 1); - // We now don't expect suspension unless an exception is thrown. // Read the value. Primitive::Type field_type = f->GetTypeAsPrimitiveType(); @@ -356,15 +351,11 @@ static void Field_set(JNIEnv* env, jobject javaField, jobject javaObj, jobject j DCHECK(soa.Self()->IsExceptionPending()); return; } - // If field is not set to be accessible, verify it can be accessed by the caller. if (!f->IsAccessible() && !VerifyFieldAccess<true>(soa.Self(), f, o)) { DCHECK(soa.Self()->IsExceptionPending()); return; } - - hiddenapi::MaybeWarnAboutMemberAccess(f->GetArtField(), soa.Self(), /* num_frames */ 1); - SetFieldValue(o, f, field_prim_type, true, unboxed_value); } @@ -400,8 +391,6 @@ static void SetPrimitiveField(JNIEnv* env, return; } - hiddenapi::MaybeWarnAboutMemberAccess(f->GetArtField(), soa.Self(), /* num_frames */ 1); - // Write the value. SetFieldValue(o, f, field_type, false, wide_value); } diff --git a/runtime/reflection.cc b/runtime/reflection.cc index 6ffafe02f1..635a03afe0 100644 --- a/runtime/reflection.cc +++ b/runtime/reflection.cc @@ -465,9 +465,6 @@ JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, jobject o } ArtMethod* method = jni::DecodeArtMethod(mid); - - hiddenapi::MaybeWarnAboutMemberAccess(method, soa.Self(), /* num_frames */ 1); - bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor(); if (is_string_init) { // Replace calls to String.<init> with equivalent StringFactory call. @@ -499,9 +496,6 @@ JValue InvokeWithJValues(const ScopedObjectAccessAlreadyRunnable& soa, jobject o } ArtMethod* method = jni::DecodeArtMethod(mid); - - hiddenapi::MaybeWarnAboutMemberAccess(method, soa.Self(), /* num_frames */ 1); - bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor(); if (is_string_init) { // Replace calls to String.<init> with equivalent StringFactory call. @@ -534,9 +528,6 @@ JValue InvokeVirtualOrInterfaceWithJValues(const ScopedObjectAccessAlreadyRunnab ObjPtr<mirror::Object> receiver = soa.Decode<mirror::Object>(obj); ArtMethod* method = FindVirtualMethod(receiver, jni::DecodeArtMethod(mid)); - - hiddenapi::MaybeWarnAboutMemberAccess(method, soa.Self(), /* num_frames */ 1); - bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor(); if (is_string_init) { // Replace calls to String.<init> with equivalent StringFactory call. @@ -569,9 +560,6 @@ JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnab ObjPtr<mirror::Object> receiver = soa.Decode<mirror::Object>(obj); ArtMethod* method = FindVirtualMethod(receiver, jni::DecodeArtMethod(mid)); - - hiddenapi::MaybeWarnAboutMemberAccess(method, soa.Self(), /* num_frames */ 1); - bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor(); if (is_string_init) { // Replace calls to String.<init> with equivalent StringFactory call. @@ -616,8 +604,6 @@ jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject javaM } } - hiddenapi::MaybeWarnAboutMemberAccess(m, soa.Self(), num_frames); - ObjPtr<mirror::Object> receiver; if (!m->IsStatic()) { // Replace calls to String.<init> with equivalent StringFactory call. |