Start enforcing hidden API blacklist
Insert checks into reflection, JNI and Class::CanAccessMember to
make blacklisted hidden APIs undiscoverable.
The change was tested with internal microbenchmarks of reflection
and those showed no measurable performance impact.
Test: art/test.py -b -r -t 674-hiddenapi
Bug: 64382372
Change-Id: I9e39804b837ae9ffeba926f2a5b1e8e9986c472b
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h
new file mode 100644
index 0000000..241850e
--- /dev/null
+++ b/runtime/hidden_api.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 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_HIDDEN_API_H_
+#define ART_RUNTIME_HIDDEN_API_H_
+
+#include "hidden_api_access_flags.h"
+#include "reflection.h"
+#include "runtime.h"
+
+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 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.
+}
+
+} // namespace hiddenapi
+} // namespace art
+
+#endif // ART_RUNTIME_HIDDEN_API_H_
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index d1436fa..b9b0051 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -38,6 +38,7 @@
#include "entrypoints/entrypoint_utils-inl.h"
#include "gc/reference_processor.h"
#include "handle_scope-inl.h"
+#include "hidden_api.h"
#include "interpreter/interpreter_common.h"
#include "jvalue-inl.h"
#include "mirror/array-inl.h"
@@ -265,7 +266,11 @@
bool ok = false;
auto* cl = Runtime::Current()->GetClassLinker();
if (cl->EnsureInitialized(self, h_klass, true, true)) {
- auto* cons = h_klass->FindConstructor("()V", cl->GetImagePointerSize());
+ ArtMethod* cons = h_klass->FindConstructor("()V", cl->GetImagePointerSize());
+ if (cons != nullptr &&
+ hiddenapi::ShouldBlockAccessToMember(cons, shadow_frame->GetMethod())) {
+ cons = nullptr;
+ }
if (cons != nullptr) {
Handle<mirror::Object> h_obj(hs.NewHandle(klass->AllocObject(self)));
CHECK(h_obj != nullptr); // We don't expect OOM at compile-time.
@@ -308,6 +313,10 @@
}
}
}
+ if (found != nullptr &&
+ hiddenapi::ShouldBlockAccessToMember(found, shadow_frame->GetMethod())) {
+ found = nullptr;
+ }
if (found == nullptr) {
AbortTransactionOrFail(self, "Failed to find field in Class.getDeclaredField in un-started "
" runtime. name=%s class=%s", name2->ToModifiedUtf8().c_str(),
@@ -370,6 +379,10 @@
self, klass, name, args);
}
}
+ if (method != nullptr &&
+ hiddenapi::ShouldBlockAccessToMember(method->GetArtMethod(), shadow_frame->GetMethod())) {
+ method = nullptr;
+ }
result->SetL(method);
}
@@ -404,6 +417,11 @@
false>(self, klass, args);
}
}
+ if (constructor != nullptr &&
+ hiddenapi::ShouldBlockAccessToMember(
+ constructor->GetArtMethod(), shadow_frame->GetMethod())) {
+ constructor = nullptr;
+ }
result->SetL(constructor);
}
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index b8e6ebe..727f54b 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -34,6 +34,7 @@
#include "class_linker-inl.h"
#include "dex/dex_file-inl.h"
#include "fault_handler.h"
+#include "hidden_api.h"
#include "gc/accounting/card_table-inl.h"
#include "gc_root.h"
#include "indirect_reference_table-inl.h"
@@ -238,6 +239,10 @@
} else {
method = c->FindClassMethod(name, sig, pointer_size);
}
+ if (method != nullptr &&
+ hiddenapi::ShouldBlockAccessToMember(method, soa.Self(), /* num_frames */ 1)) {
+ method = nullptr;
+ }
if (method == nullptr || method->IsStatic() != is_static) {
ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static");
return nullptr;
@@ -314,6 +319,10 @@
} else {
field = c->FindInstanceField(name, field_type->GetDescriptor(&temp));
}
+ if (field != nullptr &&
+ hiddenapi::ShouldBlockAccessToMember(field, soa.Self(), /* num_frames */ 1)) {
+ field = nullptr;
+ }
if (field == nullptr) {
soa.Self()->ThrowNewExceptionF("Ljava/lang/NoSuchFieldError;",
"no \"%s\" field \"%s\" in class \"%s\" or its superclasses",
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index cd313b3..36388eb 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -30,6 +30,7 @@
#include "dex/invoke_type.h"
#include "dex_cache.h"
#include "gc/heap-inl.h"
+#include "hidden_api.h"
#include "iftable.h"
#include "subtype_check.h"
#include "object-inl.h"
@@ -1143,6 +1144,10 @@
if (this == access_to) {
return true;
}
+ // Do not allow non-boot class path classes access hidden APIs.
+ if (hiddenapi::ShouldBlockAccessToMember(member_flags, this)) {
+ return false;
+ }
// Public members are trivially accessible
if (member_flags & kAccPublic) {
return true;
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index e22726b..2892967 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -286,7 +286,7 @@
}
if ((runtime_flags & DISABLE_HIDDEN_API_CHECKS) != 0) {
- Runtime::Current()->DisableHiddenApiChecks();
+ Runtime::Current()->SetHiddenApiChecksEnabled(false);
runtime_flags &= ~DISABLE_HIDDEN_API_CHECKS;
}
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index 7b999c0..4d36e80 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -25,6 +25,7 @@
#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/class_loader.h"
@@ -47,6 +48,75 @@
namespace art {
+ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!Runtime::Current()->AreHiddenApiChecksEnabled()) {
+ return false;
+ }
+
+ // 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 {
+ explicit FirstNonClassClassCallerVisitor(Thread* thread)
+ : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ caller(nullptr) {
+ }
+
+ bool VisitFrame() REQUIRES_SHARED(Locks::mutator_lock_) {
+ ArtMethod *m = GetMethod();
+ if (m == nullptr) {
+ // Attached native thread. Assume this is *not* boot class path.
+ caller = nullptr;
+ return false;
+ } else if (m->IsRuntimeMethod()) {
+ // Internal runtime method, continue walking the stack.
+ return true;
+ } else if (m->GetDeclaringClass()->IsClassClass()) {
+ return true;
+ } else {
+ caller = m;
+ return false;
+ }
+ }
+
+ ArtMethod* caller;
+ };
+
+ FirstNonClassClassCallerVisitor visitor(self);
+ visitor.WalkStack();
+ return visitor.caller == nullptr ||
+ !visitor.caller->GetDeclaringClass()->IsBootStrapClassLoaded();
+}
+
+// Returns true if the first non-ClassClass caller up the stack should not be
+// allowed access to `member`.
+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);
+}
+
+// Returns true if a class member should be discoverable with reflection given
+// the criteria. Some reflection calls only return public members
+// (public_only == true), some members should be hidden from non-boot class path
+// callers (enforce_hidden_api == true).
+ALWAYS_INLINE static bool IsDiscoverable(bool public_only,
+ bool enforce_hidden_api,
+ uint32_t access_flags) {
+ if (public_only && ((access_flags & kAccPublic) == 0)) {
+ return false;
+ }
+
+ if (enforce_hidden_api && hiddenapi::IsMemberHidden(access_flags)) {
+ return false;
+ }
+
+ return true;
+}
+
+
ALWAYS_INLINE static inline ObjPtr<mirror::Class> DecodeClass(
const ScopedFastNativeObjectAccess& soa, jobject java_class)
REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -164,17 +234,16 @@
IterationRange<StrideIterator<ArtField>> ifields = klass->GetIFields();
IterationRange<StrideIterator<ArtField>> sfields = klass->GetSFields();
size_t array_size = klass->NumInstanceFields() + klass->NumStaticFields();
- if (public_only) {
- // Lets go subtract all the non public fields.
- for (ArtField& field : ifields) {
- if (!field.IsPublic()) {
- --array_size;
- }
+ bool enforce_hidden_api = ShouldEnforceHiddenApi(self);
+ // Lets go subtract all the non discoverable fields.
+ for (ArtField& field : ifields) {
+ if (!IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) {
+ --array_size;
}
- for (ArtField& field : sfields) {
- if (!field.IsPublic()) {
- --array_size;
- }
+ }
+ for (ArtField& field : sfields) {
+ if (!IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) {
+ --array_size;
}
}
size_t array_idx = 0;
@@ -184,7 +253,7 @@
return nullptr;
}
for (ArtField& field : ifields) {
- if (!public_only || field.IsPublic()) {
+ if (IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) {
auto* reflect_field = mirror::Field::CreateFromArtField<kRuntimePointerSize>(self,
&field,
force_resolve);
@@ -199,7 +268,7 @@
}
}
for (ArtField& field : sfields) {
- if (!public_only || field.IsPublic()) {
+ if (IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) {
auto* reflect_field = mirror::Field::CreateFromArtField<kRuntimePointerSize>(self,
&field,
force_resolve);
@@ -354,8 +423,13 @@
ThrowNullPointerException("name == null");
return nullptr;
}
- return soa.AddLocalReference<jobject>(
- GetPublicFieldRecursive(soa.Self(), DecodeClass(soa, javaThis), name_string));
+
+ mirror::Field* field = GetPublicFieldRecursive(
+ soa.Self(), DecodeClass(soa, javaThis), name_string);
+ if (field == nullptr || ShouldBlockAccessToMember(field->GetArtField(), soa.Self())) {
+ return nullptr;
+ }
+ return soa.AddLocalReference<jobject>(field);
}
static jobject Class_getDeclaredField(JNIEnv* env, jobject javaThis, jstring name) {
@@ -369,7 +443,7 @@
Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis));
Handle<mirror::Field> result =
hs.NewHandle(GetDeclaredField(soa.Self(), h_klass.Get(), h_string.Get()));
- if (result == nullptr) {
+ if (result == nullptr || ShouldBlockAccessToMember(result->GetArtField(), soa.Self())) {
std::string name_str = h_string->ToModifiedUtf8();
if (name_str == "value" && h_klass->IsStringClass()) {
// We log the error for this specific case, as the user might just swallow the exception.
@@ -399,24 +473,32 @@
soa.Self(),
DecodeClass(soa, javaThis),
soa.Decode<mirror::ObjectArray<mirror::Class>>(args));
+ if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
+ return nullptr;
+ }
return soa.AddLocalReference<jobject>(result);
}
-static ALWAYS_INLINE inline bool MethodMatchesConstructor(ArtMethod* m, bool public_only)
+static ALWAYS_INLINE inline bool MethodMatchesConstructor(
+ ArtMethod* m, bool public_only, bool enforce_hidden_api)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(m != nullptr);
- return (!public_only || m->IsPublic()) && !m->IsStatic() && m->IsConstructor();
+ return m->IsConstructor() &&
+ !m->IsStatic() &&
+ IsDiscoverable(public_only, enforce_hidden_api, m->GetAccessFlags());
}
static jobjectArray Class_getDeclaredConstructorsInternal(
JNIEnv* env, jobject javaThis, jboolean publicOnly) {
ScopedFastNativeObjectAccess soa(env);
StackHandleScope<2> hs(soa.Self());
+ bool public_only = (publicOnly != JNI_FALSE);
+ bool enforce_hidden_api = ShouldEnforceHiddenApi(soa.Self());
Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis));
size_t constructor_count = 0;
// Two pass approach for speed.
for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) {
- constructor_count += MethodMatchesConstructor(&m, publicOnly != JNI_FALSE) ? 1u : 0u;
+ constructor_count += MethodMatchesConstructor(&m, public_only, enforce_hidden_api) ? 1u : 0u;
}
auto h_constructors = hs.NewHandle(mirror::ObjectArray<mirror::Constructor>::Alloc(
soa.Self(), mirror::Constructor::ArrayClass(), constructor_count));
@@ -426,7 +508,7 @@
}
constructor_count = 0;
for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) {
- if (MethodMatchesConstructor(&m, publicOnly != JNI_FALSE)) {
+ if (MethodMatchesConstructor(&m, public_only, enforce_hidden_api)) {
DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
DCHECK(!Runtime::Current()->IsActiveTransaction());
auto* constructor = mirror::Constructor::CreateFromArtMethod<kRuntimePointerSize, false>(
@@ -452,6 +534,9 @@
DecodeClass(soa, javaThis),
soa.Decode<mirror::String>(name),
soa.Decode<mirror::ObjectArray<mirror::Class>>(args));
+ if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
+ return nullptr;
+ }
return soa.AddLocalReference<jobject>(result);
}
@@ -459,13 +544,17 @@
jboolean publicOnly) {
ScopedFastNativeObjectAccess soa(env);
StackHandleScope<2> hs(soa.Self());
+
+ bool enforce_hidden_api = ShouldEnforceHiddenApi(soa.Self());
+ bool public_only = (publicOnly != JNI_FALSE);
+
Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis));
size_t num_methods = 0;
- for (auto& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
- auto modifiers = m.GetAccessFlags();
+ for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
+ uint32_t modifiers = m.GetAccessFlags();
// Add non-constructor declared methods.
- if ((publicOnly == JNI_FALSE || (modifiers & kAccPublic) != 0) &&
- (modifiers & kAccConstructor) == 0) {
+ if ((modifiers & kAccConstructor) == 0 &&
+ IsDiscoverable(public_only, enforce_hidden_api, modifiers)) {
++num_methods;
}
}
@@ -476,10 +565,10 @@
return nullptr;
}
num_methods = 0;
- for (auto& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
- auto modifiers = m.GetAccessFlags();
- if ((publicOnly == JNI_FALSE || (modifiers & kAccPublic) != 0) &&
- (modifiers & kAccConstructor) == 0) {
+ for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
+ uint32_t modifiers = m.GetAccessFlags();
+ if ((modifiers & kAccConstructor) == 0 &&
+ IsDiscoverable(public_only, enforce_hidden_api, modifiers)) {
DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
DCHECK(!Runtime::Current()->IsActiveTransaction());
auto* method =
@@ -693,11 +782,11 @@
return nullptr;
}
}
- auto* constructor = klass->GetDeclaredConstructor(
+ ArtMethod* constructor = klass->GetDeclaredConstructor(
soa.Self(),
ScopedNullHandle<mirror::ObjectArray<mirror::Class>>(),
kRuntimePointerSize);
- if (UNLIKELY(constructor == nullptr)) {
+ if (UNLIKELY(constructor == nullptr) || ShouldBlockAccessToMember(constructor, soa.Self())) {
soa.Self()->ThrowNewExceptionF("Ljava/lang/InstantiationException;",
"%s has no zero argument constructor",
klass->PrettyClass().c_str());
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 022a1be..893ebbe 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -520,8 +520,8 @@
bool IsVerificationEnabled() const;
bool IsVerificationSoftFail() const;
- void DisableHiddenApiChecks() {
- do_hidden_api_checks_ = false;
+ void SetHiddenApiChecksEnabled(bool value) {
+ do_hidden_api_checks_ = value;
}
bool AreHiddenApiChecksEnabled() const {
diff --git a/runtime/verifier/reg_type-inl.h b/runtime/verifier/reg_type-inl.h
index f719782..9e12d63 100644
--- a/runtime/verifier/reg_type-inl.h
+++ b/runtime/verifier/reg_type-inl.h
@@ -48,9 +48,6 @@
inline bool RegType::CanAccessMember(ObjPtr<mirror::Class> klass, uint32_t access_flags) const {
DCHECK(IsReferenceTypes());
- if ((access_flags & kAccPublic) != 0) {
- return true;
- }
if (IsNull()) {
return true;
}
diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc
index 672079b..89cf68c 100644
--- a/test/674-hiddenapi/hiddenapi.cc
+++ b/test/674-hiddenapi/hiddenapi.cc
@@ -25,6 +25,10 @@
namespace art {
namespace Test674HiddenApi {
+extern "C" JNIEXPORT void JNICALL Java_Main_init(JNIEnv*, jclass) {
+ Runtime::Current()->SetHiddenApiChecksEnabled(true);
+}
+
extern "C" JNIEXPORT void JNICALL Java_Main_appendToBootClassLoader(
JNIEnv* env, jclass, jstring jpath) {
ScopedUtfChars utf(env, jpath);
diff --git a/test/674-hiddenapi/src-art/Main.java b/test/674-hiddenapi/src-art/Main.java
index 9773e0a..9c691ad 100644
--- a/test/674-hiddenapi/src-art/Main.java
+++ b/test/674-hiddenapi/src-art/Main.java
@@ -31,6 +31,9 @@
System.loadLibrary(args[0]);
prepareNativeLibFileName(args[0]);
+ // Enable hidden API checks in case they are disabled by default.
+ init();
+
// Run test with both parent and child dex files loaded with class loaders.
// The expectation is that hidden members in parent should be visible to
// the child.
@@ -141,4 +144,5 @@
private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
private static native void appendToBootClassLoader(String dexPath);
+ private static native void init();
}
diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java
index af615bf..be2a352 100644
--- a/test/674-hiddenapi/src-ex/ChildClass.java
+++ b/test/674-hiddenapi/src-ex/ChildClass.java
@@ -93,9 +93,18 @@
"in boot class path");
}
+ boolean isSameBoot = (isParentInBoot == isChildInBoot);
+
// Run meaningful combinations of access flags.
for (Hiddenness hiddenness : Hiddenness.values()) {
- final Behaviour expected = Behaviour.Granted;
+ final Behaviour expected;
+ if (isSameBoot || hiddenness == Hiddenness.Whitelist) {
+ expected = Behaviour.Granted;
+ } else if (hiddenness == Hiddenness.Blacklist) {
+ expected = Behaviour.Denied;
+ } else {
+ expected = Behaviour.Granted;
+ }
for (boolean isStatic : booleanValues) {
String suffix = (isStatic ? "Static" : "") + hiddenness.name();