From 26d7b166bada897a522de80086cfa580438723f8 Mon Sep 17 00:00:00 2001 From: Jeremiah Jordan Date: Tue, 23 Jun 2026 13:49:14 -0500 Subject: [PATCH] Verify extension type before initializing reflectively-loaded classes Cassandra resolves pluggable extensions by class name from configuration, schema, and tooling inputs. These names were loaded with an initializing Class.forName(name) and type-checked only afterward, so the named class ran its static initializer before its type was confirmed. After this change such classes will be loaded without initialization, verified against the expected interface or base class, and initialized only through normal use after validation. A shared FBUtilities.classForNameWithoutInitialization helper and typed instanceOrConstruct/construct overloads apply this to the configurable extension points loaded by class name: the authentication, authorization, role-management, network and internode-authenticator backends, the partitioner, audit logger, configuration loader, seed provider, snitch, abstract types, secondary and custom indexes, compaction strategy, compressor, replication strategy, SASI analyzers, key and cache providers, query handler, storage and stream hooks, tracing, the JMX authorization proxy, MBeans, the monotonic clock, nodetool Sjk, triggers, the sstableloader and stress class options, and diagnostic event classes (loaded without initialization and checked against DiagnosticEvent, preserving the InvalidClassException contract and the existing package restriction). Regression tests confirm that an invalid-type load is rejected without initializing the target class, and that valid implementations still resolve. Hadoop client integration and hard-coded JDK and internal class probes are left unchanged. patch by Jeremiah Jordan; reviewed by Stefan Miklosovic for CASSANDRA-21525 --- CHANGES.txt | 1 + .../org/apache/cassandra/auth/AuthConfig.java | 2 +- .../cassandra/config/DatabaseDescriptor.java | 9 +- .../org/apache/cassandra/db/StorageHook.java | 2 +- .../cassandra/db/marshal/TypeParser.java | 19 +++- .../diag/DiagnosticEventPersistence.java | 6 +- .../index/SecondaryIndexManager.java | 7 +- .../cassandra/index/sasi/conf/IndexMode.java | 49 +++++---- .../locator/AbstractReplicationStrategy.java | 10 +- .../cassandra/schema/CompactionParams.java | 10 +- .../cassandra/schema/CompressionParams.java | 13 ++- .../cassandra/schema/IndexMetadata.java | 4 +- .../cassandra/security/CipherFactory.java | 8 +- .../cassandra/service/CacheService.java | 8 +- .../apache/cassandra/service/ClientState.java | 2 +- .../cassandra/streaming/StreamHook.java | 2 +- .../apache/cassandra/tools/LoaderOptions.java | 14 +-- .../apache/cassandra/tools/nodetool/Sjk.java | 3 +- .../org/apache/cassandra/tracing/Tracing.java | 2 +- .../cassandra/triggers/TriggerExecutor.java | 18 +++- .../apache/cassandra/utils/FBUtilities.java | 102 ++++++++++++++++-- .../cassandra/utils/JMXServerUtils.java | 2 +- .../apache/cassandra/utils/MBeanWrapper.java | 2 +- .../cassandra/utils/MonotonicClock.java | 5 +- .../config/DatabaseDescriptorTest.java | 23 ++++ .../cassandra/db/marshal/TypeParserTest.java | 15 +++ .../diag/ClassLoadingTestDiagnosticEvent.java | 45 ++++++++ .../diag/DiagnosticEventPersistenceTest.java | 71 ++++++++++++ .../index/SecondaryIndexManagerTest.java | 15 +++ .../index/sasi/conf/IndexModeTest.java | 53 ++++++++- .../schema/CompactionParamsTest.java | 44 ++++++++ .../schema/CompressionParamsTest.java | 48 +++++++++ .../cassandra/schema/IndexMetadataTest.java | 31 ++++++ .../cassandra/security/CipherFactoryTest.java | 22 ++++ .../triggers/TriggerExecutorTest.java | 27 +++++ .../utils/ClassLoadingTestNonAssignable.java | 31 ++++++ .../utils/ClassLoadingTestSupport.java | 47 ++++++++ .../cassandra/utils/FBUtilitiesTest.java | 62 +++++++++++ .../stress/settings/OptionReplication.java | 7 +- .../stress/settings/SettingsMode.java | 8 +- 40 files changed, 748 insertions(+), 101 deletions(-) create mode 100644 test/unit/org/apache/cassandra/diag/ClassLoadingTestDiagnosticEvent.java create mode 100644 test/unit/org/apache/cassandra/diag/DiagnosticEventPersistenceTest.java create mode 100644 test/unit/org/apache/cassandra/schema/CompactionParamsTest.java create mode 100644 test/unit/org/apache/cassandra/schema/CompressionParamsTest.java create mode 100644 test/unit/org/apache/cassandra/utils/ClassLoadingTestNonAssignable.java create mode 100644 test/unit/org/apache/cassandra/utils/ClassLoadingTestSupport.java diff --git a/CHANGES.txt b/CHANGES.txt index f333ddb532..8684c87212 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.0.21 + * Verify extension type before initializing reflectively-loaded classes (CASSANDRA-21525) * Rename conflicting nodetool import --copy-data short option from -p to -cd (CASSANDRA-20214) * Fix PasswordObfuscator failing to obfuscate certain passwords (CASSANDRA-21113) * Fix negative memtable allocator ownership when an update is shadowed by an existing row deletion (CASSANDRA-21469) diff --git a/src/java/org/apache/cassandra/auth/AuthConfig.java b/src/java/org/apache/cassandra/auth/AuthConfig.java index cc38296240..2ecd373633 100644 --- a/src/java/org/apache/cassandra/auth/AuthConfig.java +++ b/src/java/org/apache/cassandra/auth/AuthConfig.java @@ -95,7 +95,7 @@ public final class AuthConfig // authenticator if (conf.internode_authenticator != null) - DatabaseDescriptor.setInternodeAuthenticator(FBUtilities.construct(conf.internode_authenticator, "internode_authenticator")); + DatabaseDescriptor.setInternodeAuthenticator(FBUtilities.construct(conf.internode_authenticator, "internode_authenticator", IInternodeAuthenticator.class)); // network authorizer INetworkAuthorizer networkAuthorizer = FBUtilities.newNetworkAuthorizer(conf.network_authorizer); diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index 593235d577..00a4aa912f 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -297,7 +297,7 @@ public class DatabaseDescriptor String loaderClass = System.getProperty(Config.PROPERTY_PREFIX + "config.loader"); ConfigurationLoader loader = loaderClass == null ? new YamlConfigurationLoader() - : FBUtilities.construct(loaderClass, "configuration loading"); + : FBUtilities.construct(loaderClass, "configuration loading", ConfigurationLoader.class); Config config = loader.loadConfig(); if (!hasLoggedConfig) @@ -1036,8 +1036,9 @@ public class DatabaseDescriptor } try { - Class seedProviderClass = Class.forName(conf.seed_provider.class_name); - seedProvider = (SeedProvider)seedProviderClass.getConstructor(Map.class).newInstance(conf.seed_provider.parameters); + Class seedProviderClass = + FBUtilities.classForNameWithoutInitialization(conf.seed_provider.class_name, "seed provider", SeedProvider.class); + seedProvider = seedProviderClass.getConstructor(Map.class).newInstance(conf.seed_provider.parameters); } // there are about 5 checked exceptions that could be thrown here. catch (Exception e) @@ -1243,7 +1244,7 @@ public class DatabaseDescriptor { if (!snitchClassName.contains(".")) snitchClassName = "org.apache.cassandra.locator." + snitchClassName; - IEndpointSnitch snitch = FBUtilities.construct(snitchClassName, "snitch"); + IEndpointSnitch snitch = FBUtilities.construct(snitchClassName, "snitch", IEndpointSnitch.class); return dynamic ? new DynamicEndpointSnitch(snitch) : snitch; } diff --git a/src/java/org/apache/cassandra/db/StorageHook.java b/src/java/org/apache/cassandra/db/StorageHook.java index be1d0bf754..0289780039 100644 --- a/src/java/org/apache/cassandra/db/StorageHook.java +++ b/src/java/org/apache/cassandra/db/StorageHook.java @@ -53,7 +53,7 @@ public interface StorageHook String className = System.getProperty("cassandra.storage_hook"); if (className != null) { - return FBUtilities.construct(className, StorageHook.class.getSimpleName()); + return FBUtilities.construct(className, StorageHook.class.getSimpleName(), StorageHook.class); } return new StorageHook() diff --git a/src/java/org/apache/cassandra/db/marshal/TypeParser.java b/src/java/org/apache/cassandra/db/marshal/TypeParser.java index db0d33d251..fe92207019 100644 --- a/src/java/org/apache/cassandra/db/marshal/TypeParser.java +++ b/src/java/org/apache/cassandra/db/marshal/TypeParser.java @@ -353,8 +353,7 @@ public class TypeParser private static AbstractType getAbstractType(String compareWith) throws ConfigurationException { - String className = compareWith.contains(".") ? compareWith : "org.apache.cassandra.db.marshal." + compareWith; - Class> typeClass = FBUtilities.>classForName(className, "abstract-type"); + Class> typeClass = getAbstractTypeClass(compareWith); try { Field field = typeClass.getDeclaredField("instance"); @@ -367,10 +366,22 @@ public class TypeParser } } - private static AbstractType getAbstractType(String compareWith, TypeParser parser) throws SyntaxException, ConfigurationException + private static Class> getAbstractTypeClass(String compareWith) throws ConfigurationException { String className = compareWith.contains(".") ? compareWith : "org.apache.cassandra.db.marshal." + compareWith; - Class> typeClass = FBUtilities.>classForName(className, "abstract-type"); + // Defer class initialization until after confirming this is an AbstractType. The static instance field + // access or getInstance(TypeParser) invocation below performs the initialization for valid types. + @SuppressWarnings("unchecked") + Class> typeClass = + (Class>) FBUtilities.classForNameWithoutInitialization(className, + "abstract-type", + AbstractType.class); + return typeClass; + } + + private static AbstractType getAbstractType(String compareWith, TypeParser parser) throws SyntaxException, ConfigurationException + { + Class> typeClass = getAbstractTypeClass(compareWith); try { Method method = typeClass.getDeclaredMethod("getInstance", TypeParser.class); diff --git a/src/java/org/apache/cassandra/diag/DiagnosticEventPersistence.java b/src/java/org/apache/cassandra/diag/DiagnosticEventPersistence.java index 7da335ca04..82d23746fe 100644 --- a/src/java/org/apache/cassandra/diag/DiagnosticEventPersistence.java +++ b/src/java/org/apache/cassandra/diag/DiagnosticEventPersistence.java @@ -129,6 +129,7 @@ public final class DiagnosticEventPersistence LastEventIdBroadcaster.instance().setLastEventId(event.getClass().getName(), store.getLastEventId()); } + @SuppressWarnings("unchecked") private Class getEventClass(String eventClazz) throws ClassNotFoundException, InvalidClassException { // get class by eventClazz argument name @@ -136,12 +137,13 @@ public final class DiagnosticEventPersistence if (!eventClazz.startsWith("org.apache.cassandra.")) throw new RuntimeException("Not a Cassandra event class: " + eventClazz); - Class clazz = (Class) Class.forName(eventClazz); + // Load without initialization so the type can be verified before the class's static initializer runs. + Class clazz = Class.forName(eventClazz, false, DiagnosticEventPersistence.class.getClassLoader()); if (!(DiagnosticEvent.class.isAssignableFrom(clazz))) throw new InvalidClassException("Event class must be of type DiagnosticEvent"); - return clazz; + return (Class) clazz.asSubclass(DiagnosticEvent.class); } private DiagnosticEventStore getStore(Class cls) diff --git a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java index aa8feb295c..53037bf445 100644 --- a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java +++ b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java @@ -767,6 +767,11 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum return indexes.get(indexName); } + static Class loadIndexClass(String className) + { + return FBUtilities.classForNameWithoutInitialization(className, "Index", Index.class); + } + private Index createInstance(IndexMetadata indexDef) { Index newIndex; @@ -777,7 +782,7 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum assert !Strings.isNullOrEmpty(className); try { - Class indexClass = FBUtilities.classForName(className, "Index"); + Class indexClass = loadIndexClass(className); Constructor ctor = indexClass.getConstructor(ColumnFamilyStore.class, IndexMetadata.class); newIndex = ctor.newInstance(baseCfs, indexDef); } diff --git a/src/java/org/apache/cassandra/index/sasi/conf/IndexMode.java b/src/java/org/apache/cassandra/index/sasi/conf/IndexMode.java index 875d2f7b28..4f2ad4c824 100644 --- a/src/java/org/apache/cassandra/index/sasi/conf/IndexMode.java +++ b/src/java/org/apache/cassandra/index/sasi/conf/IndexMode.java @@ -34,6 +34,7 @@ import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.index.sasi.plan.Expression.Op; import org.apache.cassandra.schema.IndexMetadata; +import org.apache.cassandra.utils.FBUtilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,10 +61,10 @@ public class IndexMode public final Mode mode; public final boolean isAnalyzed, isLiteral; - public final Class analyzerClass; + public final Class analyzerClass; public final long maxCompactionFlushMemoryInBytes; - private IndexMode(Mode mode, boolean isLiteral, boolean isAnalyzed, Class analyzerClass, long maxMemBytes) + private IndexMode(Mode mode, boolean isLiteral, boolean isAnalyzed, Class analyzerClass, long maxMemBytes) { this.mode = mode; this.isLiteral = isLiteral; @@ -81,7 +82,7 @@ public class IndexMode if (isAnalyzed) { if (analyzerClass != null) - analyzer = (AbstractAnalyzer) analyzerClass.newInstance(); + analyzer = analyzerClass.newInstance(); else if (TOKENIZABLE_TYPES.contains(validator)) analyzer = new StandardAnalyzer(); } @@ -99,21 +100,14 @@ public class IndexMode // validate that a valid analyzer class was provided if specified if (indexOptions.containsKey(INDEX_ANALYZER_CLASS_OPTION)) { - Class analyzerClass; - try - { - analyzerClass = Class.forName(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION)); - } - catch (ClassNotFoundException e) - { - throw new ConfigurationException(String.format("Invalid analyzer class option specified [%s]", - indexOptions.get(INDEX_ANALYZER_CLASS_OPTION))); - } + Class analyzerClass = FBUtilities.classForNameWithoutInitialization(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION), + "analyzer", + AbstractAnalyzer.class); AbstractAnalyzer analyzer; try { - analyzer = (AbstractAnalyzer) analyzerClass.newInstance(); + analyzer = analyzerClass.newInstance(); analyzer.validate(indexOptions, cd); } catch (InstantiationException | IllegalAccessException e) @@ -148,25 +142,30 @@ public class IndexMode } boolean isAnalyzed = false; - Class analyzerClass = null; - try + Class analyzerClass = null; + if (indexOptions.get(INDEX_ANALYZER_CLASS_OPTION) != null) { - if (indexOptions.get(INDEX_ANALYZER_CLASS_OPTION) != null) + try { - analyzerClass = Class.forName(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION)); + analyzerClass = FBUtilities.classForNameWithoutInitialization(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION), + "analyzer", + AbstractAnalyzer.class); isAnalyzed = indexOptions.get(INDEX_ANALYZED_OPTION) == null - ? true : Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION)); + ? true : Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION)); } - else if (indexOptions.get(INDEX_ANALYZED_OPTION) != null) + catch (ConfigurationException e) { - isAnalyzed = Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION)); + if (!(e.getCause() instanceof ClassNotFoundException)) + throw e; + + // Should not happen as we already validated we could instantiate an instance in validateAnalyzer(). + logger.error("Failed to find specified analyzer class [{}]. Falling back to default analyzer", + indexOptions.get(INDEX_ANALYZER_CLASS_OPTION)); } } - catch (ClassNotFoundException e) + else if (indexOptions.get(INDEX_ANALYZED_OPTION) != null) { - // should not happen as we already validated we could instantiate an instance in validateAnalyzer() - logger.error("Failed to find specified analyzer class [{}]. Falling back to default analyzer", - indexOptions.get(INDEX_ANALYZER_CLASS_OPTION)); + isAnalyzed = Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION)); } boolean isLiteral = false; diff --git a/src/java/org/apache/cassandra/locator/AbstractReplicationStrategy.java b/src/java/org/apache/cassandra/locator/AbstractReplicationStrategy.java index f14c730101..afb01376f7 100644 --- a/src/java/org/apache/cassandra/locator/AbstractReplicationStrategy.java +++ b/src/java/org/apache/cassandra/locator/AbstractReplicationStrategy.java @@ -405,11 +405,11 @@ public abstract class AbstractReplicationStrategy if ("org.apache.cassandra.locator.OldNetworkTopologyStrategy".equals(className)) // see CASSANDRA-16301 throw new ConfigurationException("The support for the OldNetworkTopologyStrategy has been removed in C* version 4.0. The keyspace strategy should be switch to NetworkTopologyStrategy"); - Class strategyClass = FBUtilities.classForName(className, "replication strategy"); - if (!AbstractReplicationStrategy.class.isAssignableFrom(strategyClass)) - { - throw new ConfigurationException(String.format("Specified replication strategy class (%s) is not derived from AbstractReplicationStrategy", className)); - } + @SuppressWarnings("unchecked") + Class strategyClass = + (Class) FBUtilities.classForNameWithoutInitialization(className, + "replication strategy", + AbstractReplicationStrategy.class); return strategyClass; } diff --git a/src/java/org/apache/cassandra/schema/CompactionParams.java b/src/java/org/apache/cassandra/schema/CompactionParams.java index 485946820e..daf1f70c66 100644 --- a/src/java/org/apache/cassandra/schema/CompactionParams.java +++ b/src/java/org/apache/cassandra/schema/CompactionParams.java @@ -277,15 +277,7 @@ public final class CompactionParams String className = name.contains(".") ? name : "org.apache.cassandra.db.compaction." + name; - Class strategyClass = FBUtilities.classForName(className, "compaction strategy"); - - if (!AbstractCompactionStrategy.class.isAssignableFrom(strategyClass)) - { - throw new ConfigurationException(format("Compaction strategy class %s is not derived from AbstractReplicationStrategy", - className)); - } - - return strategyClass; + return FBUtilities.classForNameWithoutInitialization(className, "compaction strategy", AbstractCompactionStrategy.class); } /* diff --git a/src/java/org/apache/cassandra/schema/CompressionParams.java b/src/java/org/apache/cassandra/schema/CompressionParams.java index 53760a9189..14d4d22215 100644 --- a/src/java/org/apache/cassandra/schema/CompressionParams.java +++ b/src/java/org/apache/cassandra/schema/CompressionParams.java @@ -43,6 +43,7 @@ import org.apache.cassandra.io.compress.*; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.net.MessagingService; +import org.apache.cassandra.utils.FBUtilities; import static java.lang.String.format; @@ -273,7 +274,7 @@ public final class CompressionParams return maxCompressedLength; } - private static Class parseCompressorClass(String className) throws ConfigurationException + private static Class parseCompressorClass(String className) throws ConfigurationException { if (className == null || className.isEmpty()) return null; @@ -281,15 +282,17 @@ public final class CompressionParams className = className.contains(".") ? className : "org.apache.cassandra.io.compress." + className; try { - return Class.forName(className); + return FBUtilities.classForNameWithoutInitialization(className, "compression", ICompressor.class); } - catch (Exception e) + catch (ConfigurationException e) { - throw new ConfigurationException("Could not create Compression for type " + className, e); + if (e.getCause() instanceof ClassNotFoundException || e.getCause() instanceof NoClassDefFoundError) + throw new ConfigurationException("Could not create Compression for type " + className, e); + throw e; } } - private static ICompressor createCompressor(Class compressorClass, Map compressionOptions) throws ConfigurationException + private static ICompressor createCompressor(Class compressorClass, Map compressionOptions) throws ConfigurationException { if (compressorClass == null) { diff --git a/src/java/org/apache/cassandra/schema/IndexMetadata.java b/src/java/org/apache/cassandra/schema/IndexMetadata.java index a82923d538..4529b6b905 100644 --- a/src/java/org/apache/cassandra/schema/IndexMetadata.java +++ b/src/java/org/apache/cassandra/schema/IndexMetadata.java @@ -117,9 +117,7 @@ public final class IndexMetadata throw new ConfigurationException(String.format("Required option missing for index %s : %s", name, IndexTarget.CUSTOM_INDEX_OPTION_NAME)); String className = options.get(IndexTarget.CUSTOM_INDEX_OPTION_NAME); - Class indexerClass = FBUtilities.classForName(className, "custom indexer"); - if (!Index.class.isAssignableFrom(indexerClass)) - throw new ConfigurationException(String.format("Specified Indexer class (%s) does not implement the Indexer interface", className)); + Class indexerClass = FBUtilities.classForNameWithoutInitialization(className, "custom indexer", Index.class); validateCustomIndexOptions(table, indexerClass, options); } } diff --git a/src/java/org/apache/cassandra/security/CipherFactory.java b/src/java/org/apache/cassandra/security/CipherFactory.java index 3c13629208..c4f0f9d853 100644 --- a/src/java/org/apache/cassandra/security/CipherFactory.java +++ b/src/java/org/apache/cassandra/security/CipherFactory.java @@ -41,6 +41,7 @@ import org.slf4j.LoggerFactory; import io.netty.util.concurrent.FastThreadLocal; import org.apache.cassandra.config.TransparentDataEncryptionOptions; +import org.apache.cassandra.utils.FBUtilities; /** * A factory for loading encryption keys from {@link KeyProvider} instances. @@ -70,9 +71,10 @@ public class CipherFactory try { secureRandom = SecureRandom.getInstance("SHA1PRNG"); - Class keyProviderClass = (Class)Class.forName(options.key_provider.class_name); - Constructor ctor = keyProviderClass.getConstructor(TransparentDataEncryptionOptions.class); - keyProvider = (KeyProvider)ctor.newInstance(options); + Class keyProviderClass = + FBUtilities.classForNameWithoutInitialization(options.key_provider.class_name, "key provider", KeyProvider.class); + Constructor ctor = keyProviderClass.getConstructor(TransparentDataEncryptionOptions.class); + keyProvider = ctor.newInstance(options); } catch (Exception e) { diff --git a/src/java/org/apache/cassandra/service/CacheService.java b/src/java/org/apache/cassandra/service/CacheService.java index 0a281ad846..0ed31c019a 100644 --- a/src/java/org/apache/cassandra/service/CacheService.java +++ b/src/java/org/apache/cassandra/service/CacheService.java @@ -129,9 +129,11 @@ public class CacheService implements CacheServiceMBean ? DatabaseDescriptor.getRowCacheClassName() : "org.apache.cassandra.cache.NopCacheProvider"; try { - Class> cacheProviderClass = - (Class>) Class.forName(cacheProviderClassName); - cacheProvider = cacheProviderClass.newInstance(); + Class cacheProviderClass = + FBUtilities.classForNameWithoutInitialization(cacheProviderClassName, "row cache provider", CacheProvider.class); + @SuppressWarnings("unchecked") + CacheProvider typedCacheProvider = cacheProviderClass.newInstance(); + cacheProvider = typedCacheProvider; } catch (Exception e) { diff --git a/src/java/org/apache/cassandra/service/ClientState.java b/src/java/org/apache/cassandra/service/ClientState.java index adca727709..38dec1843d 100644 --- a/src/java/org/apache/cassandra/service/ClientState.java +++ b/src/java/org/apache/cassandra/service/ClientState.java @@ -107,7 +107,7 @@ public class ClientState { try { - handler = FBUtilities.construct(customHandlerClass, "QueryHandler"); + handler = FBUtilities.construct(customHandlerClass, "QueryHandler", QueryHandler.class); logger.info("Using {} as query handler for native protocol queries (as requested with -Dcassandra.custom_query_handler_class)", customHandlerClass); } catch (Exception e) diff --git a/src/java/org/apache/cassandra/streaming/StreamHook.java b/src/java/org/apache/cassandra/streaming/StreamHook.java index 86b5182c89..21fc8466c1 100644 --- a/src/java/org/apache/cassandra/streaming/StreamHook.java +++ b/src/java/org/apache/cassandra/streaming/StreamHook.java @@ -35,7 +35,7 @@ public interface StreamHook String className = System.getProperty("cassandra.stream_hook"); if (className != null) { - return FBUtilities.construct(className, StreamHook.class.getSimpleName()); + return FBUtilities.construct(className, StreamHook.class.getSimpleName(), StreamHook.class); } else { diff --git a/src/java/org/apache/cassandra/tools/LoaderOptions.java b/src/java/org/apache/cassandra/tools/LoaderOptions.java index 22729fc476..78c15e50fa 100644 --- a/src/java/org/apache/cassandra/tools/LoaderOptions.java +++ b/src/java/org/apache/cassandra/tools/LoaderOptions.java @@ -34,6 +34,7 @@ import org.apache.cassandra.config.*; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.tools.BulkLoader.CmdLineOptions; +import org.apache.cassandra.utils.FBUtilities; import com.datastax.driver.core.AuthProvider; import com.datastax.driver.core.PlainTextAuthProvider; @@ -551,11 +552,12 @@ public class LoaderOptions { try { - Class authProviderClass = Class.forName(authProviderName); - Constructor constructor = authProviderClass.getConstructor(String.class, String.class); - authProvider = (AuthProvider)constructor.newInstance(user, passwd); + Class authProviderClass = + FBUtilities.classForNameWithoutInitialization(authProviderName, "auth provider", AuthProvider.class); + Constructor constructor = authProviderClass.getConstructor(String.class, String.class); + authProvider = constructor.newInstance(user, passwd); } - catch (ClassNotFoundException e) + catch (ConfigurationException e) { errorMsg("Unknown auth provider: " + e.getMessage(), getCmdLineOptions()); } @@ -582,9 +584,9 @@ public class LoaderOptions { try { - authProvider = (AuthProvider)Class.forName(authProviderName).newInstance(); + authProvider = FBUtilities.construct(authProviderName, "auth provider", AuthProvider.class); } - catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) + catch (ConfigurationException e) { errorMsg("Unknown auth provider: " + e.getMessage(), getCmdLineOptions()); } diff --git a/src/java/org/apache/cassandra/tools/nodetool/Sjk.java b/src/java/org/apache/cassandra/tools/nodetool/Sjk.java index 3ad2c94c6e..c059d89120 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/Sjk.java +++ b/src/java/org/apache/cassandra/tools/nodetool/Sjk.java @@ -405,6 +405,7 @@ public class Sjk extends NodeToolCmd List> result = new ArrayList<>(); try { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); String path = packageName.replace('.', '/'); for (String f : findFiles(path)) { @@ -412,7 +413,7 @@ public class Sjk extends NodeToolCmd { f = f.substring(0, f.length() - ".class".length()); f = f.replace('/', '.'); - result.add(Class.forName(f)); + result.add(Class.forName(f, false, cl)); } } return result; diff --git a/src/java/org/apache/cassandra/tracing/Tracing.java b/src/java/org/apache/cassandra/tracing/Tracing.java index 7d72224012..c58ab9a4a4 100644 --- a/src/java/org/apache/cassandra/tracing/Tracing.java +++ b/src/java/org/apache/cassandra/tracing/Tracing.java @@ -119,7 +119,7 @@ public abstract class Tracing implements ExecutorLocal { try { - tracing = FBUtilities.construct(customTracingClass, "Tracing"); + tracing = FBUtilities.construct(customTracingClass, "Tracing", Tracing.class); logger.info("Using {} as tracing queries (as requested with -Dcassandra.custom_tracing_class)", customTracingClass); } catch (Exception e) diff --git a/src/java/org/apache/cassandra/triggers/TriggerExecutor.java b/src/java/org/apache/cassandra/triggers/TriggerExecutor.java index 295003fff9..b3adaa2d3b 100644 --- a/src/java/org/apache/cassandra/triggers/TriggerExecutor.java +++ b/src/java/org/apache/cassandra/triggers/TriggerExecutor.java @@ -32,6 +32,7 @@ import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.db.*; import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.exceptions.CassandraException; +import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TriggerMetadata; @@ -252,11 +253,26 @@ public class TriggerExecutor } } + public synchronized Class loadTriggerClass(String triggerClass) throws Exception + { + // Load without initialization so the type can be verified before the class's static initializer runs. + try + { + return FBUtilities.classForNameWithoutInitialization(triggerClass, "trigger", ITrigger.class, customClassLoader); + } + catch (ConfigurationException e) + { + if (e.getCause() instanceof ClassNotFoundException) + throw (ClassNotFoundException) e.getCause(); + throw e; + } + } + public synchronized ITrigger loadTriggerInstance(String triggerClass) throws Exception { // double check. if (cachedTriggers.get(triggerClass) != null) return cachedTriggers.get(triggerClass); - return (ITrigger) customClassLoader.loadClass(triggerClass).getConstructor().newInstance(); + return loadTriggerClass(triggerClass).getConstructor().newInstance(); } } diff --git a/src/java/org/apache/cassandra/utils/FBUtilities.java b/src/java/org/apache/cassandra/utils/FBUtilities.java index 63ffc63dc5..732899d674 100644 --- a/src/java/org/apache/cassandra/utils/FBUtilities.java +++ b/src/java/org/apache/cassandra/utils/FBUtilities.java @@ -634,28 +634,28 @@ public class FBUtilities assert comparator.isPresent() : "Expected a comparator for local partitioner"; return new LocalPartitioner(comparator.get()); } - return FBUtilities.instanceOrConstruct(partitionerClassName, "partitioner"); + return FBUtilities.instanceOrConstruct(partitionerClassName, "partitioner", IPartitioner.class); } public static IAuthorizer newAuthorizer(String className) throws ConfigurationException { if (!className.contains(".")) className = "org.apache.cassandra.auth." + className; - return FBUtilities.construct(className, "authorizer"); + return FBUtilities.construct(className, "authorizer", IAuthorizer.class); } public static IAuthenticator newAuthenticator(String className) throws ConfigurationException { if (!className.contains(".")) className = "org.apache.cassandra.auth." + className; - return FBUtilities.construct(className, "authenticator"); + return FBUtilities.construct(className, "authenticator", IAuthenticator.class); } public static IRoleManager newRoleManager(String className) throws ConfigurationException { if (!className.contains(".")) className = "org.apache.cassandra.auth." + className; - return FBUtilities.construct(className, "role manager"); + return FBUtilities.construct(className, "role manager", IRoleManager.class); } public static INetworkAuthorizer newNetworkAuthorizer(String className) @@ -668,9 +668,9 @@ public class FBUtilities { className = "org.apache.cassandra.auth." + className; } - return FBUtilities.construct(className, "network authorizer"); + return FBUtilities.construct(className, "network authorizer", INetworkAuthorizer.class); } - + public static IAuditLogger newAuditLogger(String className, Map parameters) throws ConfigurationException { if (!className.contains(".")) @@ -678,8 +678,9 @@ public class FBUtilities try { - Class auditLoggerClass = Class.forName(className); - return (IAuditLogger) auditLoggerClass.getConstructor(Map.class).newInstance(parameters); + Class auditLoggerClass = + FBUtilities.classForNameWithoutInitialization(className, "Audit logger", IAuditLogger.class); + return auditLoggerClass.getConstructor(Map.class).newInstance(parameters); } catch (Exception ex) { @@ -694,7 +695,7 @@ public class FBUtilities try { - FBUtilities.classForName(className, "Audit logger"); + FBUtilities.classForNameWithoutInitialization(className, "Audit logger", IAuditLogger.class); } catch (ConfigurationException e) { @@ -704,6 +705,8 @@ public class FBUtilities } /** + * Loads and initializes a class. + * * @return The Class for the given name. * @param classname Fully qualified classname. * @param readable Descriptive noun for the role the class plays. @@ -721,6 +724,53 @@ public class FBUtilities } } + /** + * Loads a class without initializing it, then verifies it extends or implements the expected base type. + * + * @return The Class for the given name. + * @param classname Fully qualified classname. + * @param readable Descriptive noun for the role the class plays. + * @param expectedType Required superclass or interface. + * @throws ConfigurationException If the class cannot be found or is not assignable to {@code expectedType}. + */ + public static Class classForNameWithoutInitialization(String classname, + String readable, + Class expectedType) throws ConfigurationException + { + return classForNameWithoutInitialization(classname, readable, expectedType, FBUtilities.class.getClassLoader()); + } + + /** + * Loads a class without initializing it, then verifies it extends or implements the expected base type. + * + * @return The Class for the given name. + * @param classname Fully qualified classname. + * @param readable Descriptive noun for the role the class plays. + * @param expectedType Required superclass or interface. + * @param classLoader ClassLoader to use. + * @throws ConfigurationException If the class cannot be found or is not assignable to {@code expectedType}. + */ + public static Class classForNameWithoutInitialization(String classname, + String readable, + Class expectedType, + ClassLoader classLoader) throws ConfigurationException + { + try + { + Class klass = Class.forName(classname, false, classLoader); + if (!expectedType.isAssignableFrom(klass)) + throw new ConfigurationException(String.format("Invalid %s class '%s': must extend or implement %s", + readable, + classname, + expectedType.getName())); + return klass.asSubclass(expectedType); + } + catch (ClassNotFoundException | NoClassDefFoundError e) + { + throw new ConfigurationException(String.format("Unable to find %s class '%s'", readable, classname), e); + } + } + /** * Constructs an instance of the given class, which must have a no-arg or default constructor. * @param classname Fully qualified classname. @@ -730,6 +780,25 @@ public class FBUtilities public static T instanceOrConstruct(String classname, String readable) throws ConfigurationException { Class cls = FBUtilities.classForName(classname, readable); + return instanceOrConstruct(cls, classname, readable); + } + + /** + * Constructs an instance of the given class, or gets its static {@code instance} field, after verifying the + * class without initializing it. + * @param classname Fully qualified classname. + * @param readable Descriptive noun for the role the class plays. + * @param expectedType Required superclass or interface. + * @throws ConfigurationException If the class cannot be found or is not assignable to {@code expectedType}. + */ + public static T instanceOrConstruct(String classname, String readable, Class expectedType) throws ConfigurationException + { + Class cls = FBUtilities.classForNameWithoutInitialization(classname, readable, expectedType); + return instanceOrConstruct(cls, classname, readable); + } + + private static T instanceOrConstruct(Class cls, String classname, String readable) throws ConfigurationException + { try { Field instance = cls.getField("instance"); @@ -754,7 +823,20 @@ public class FBUtilities return construct(cls, classname, readable); } - private static T construct(Class cls, String classname, String readable) throws ConfigurationException + /** + * Constructs an instance of the given class after verifying it without initializing it. + * @param classname Fully qualified classname. + * @param readable Descriptive noun for the role the class plays. + * @param expectedType Required superclass or interface. + * @throws ConfigurationException If the class cannot be found or is not assignable to {@code expectedType}. + */ + public static T construct(String classname, String readable, Class expectedType) throws ConfigurationException + { + Class cls = FBUtilities.classForNameWithoutInitialization(classname, readable, expectedType); + return construct(cls, classname, readable); + } + + private static T construct(Class cls, String classname, String readable) throws ConfigurationException { try { diff --git a/src/java/org/apache/cassandra/utils/JMXServerUtils.java b/src/java/org/apache/cassandra/utils/JMXServerUtils.java index bbe09e8636..b6cc95f266 100644 --- a/src/java/org/apache/cassandra/utils/JMXServerUtils.java +++ b/src/java/org/apache/cassandra/utils/JMXServerUtils.java @@ -214,7 +214,7 @@ public class JMXServerUtils String authzProxyClass = System.getProperty("cassandra.jmx.authorizer"); if (authzProxyClass != null) { - final InvocationHandler handler = FBUtilities.construct(authzProxyClass, "JMX authz proxy"); + final InvocationHandler handler = FBUtilities.construct(authzProxyClass, "JMX authz proxy", InvocationHandler.class); final Class[] interfaces = { MBeanServerForwarder.class }; Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler); diff --git a/src/java/org/apache/cassandra/utils/MBeanWrapper.java b/src/java/org/apache/cassandra/utils/MBeanWrapper.java index 0ef342da30..bf2f07d4f2 100644 --- a/src/java/org/apache/cassandra/utils/MBeanWrapper.java +++ b/src/java/org/apache/cassandra/utils/MBeanWrapper.java @@ -77,7 +77,7 @@ public interface MBeanWrapper return new PlatformMBeanWrapper(); } } - return FBUtilities.construct(klass, "mbean"); + return FBUtilities.construct(klass, "mbean", MBeanWrapper.class); } // Passing true for graceful will log exceptions instead of rethrowing them diff --git a/src/java/org/apache/cassandra/utils/MonotonicClock.java b/src/java/org/apache/cassandra/utils/MonotonicClock.java index bd69bd5722..32fc2fe46a 100644 --- a/src/java/org/apache/cassandra/utils/MonotonicClock.java +++ b/src/java/org/apache/cassandra/utils/MonotonicClock.java @@ -85,7 +85,7 @@ public interface MonotonicClock try { logger.debug("Using custom clock implementation: {}", sclock); - return (MonotonicClock) Class.forName(sclock).newInstance(); + return FBUtilities.construct(sclock, "monotonic clock", MonotonicClock.class); } catch (Exception e) { @@ -104,7 +104,8 @@ public interface MonotonicClock try { logger.debug("Using custom clock implementation: {}", sclock); - Class clazz = (Class) Class.forName(sclock); + Class clazz = + FBUtilities.classForNameWithoutInitialization(sclock, "monotonic clock", MonotonicClock.class); if (SystemClock.class.equals(clazz) && SystemClock.class.equals(precise.getClass())) return precise; diff --git a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java index 00389384a9..c11903d483 100644 --- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java +++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java @@ -34,8 +34,12 @@ import org.junit.Test; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.locator.IEndpointSnitch; +import org.apache.cassandra.utils.ClassLoadingTestNonAssignable; +import org.apache.cassandra.utils.ClassLoadingTestSupport; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -48,6 +52,25 @@ public class DatabaseDescriptorTest DatabaseDescriptor.daemonInitialization(); } + @Test + public void testCreateEndpointSnitchWrongTypeRejectedWithoutInitializing() + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + + assertThatThrownBy(() -> DatabaseDescriptor.createEndpointSnitch(false, ClassLoadingTestNonAssignable.class.getName())) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + IEndpointSnitch.class.getName()); + + assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse(); + } + + @Test + public void testCreateEndpointSnitchValidClassResolves() + { + IEndpointSnitch snitch = DatabaseDescriptor.createEndpointSnitch(false, "SimpleSnitch"); + assertThat(snitch).isNotNull(); + } + // this came as a result of CASSANDRA-995 @Test public void testConfigurationLoader() throws Exception diff --git a/test/unit/org/apache/cassandra/db/marshal/TypeParserTest.java b/test/unit/org/apache/cassandra/db/marshal/TypeParserTest.java index ecfe910d1f..46a2a5af9a 100644 --- a/test/unit/org/apache/cassandra/db/marshal/TypeParserTest.java +++ b/test/unit/org/apache/cassandra/db/marshal/TypeParserTest.java @@ -21,6 +21,8 @@ package org.apache.cassandra.db.marshal; import org.junit.BeforeClass; import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; @@ -28,6 +30,8 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.dht.*; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.SyntaxException; +import org.apache.cassandra.utils.ClassLoadingTestNonAssignable; +import org.apache.cassandra.utils.ClassLoadingTestSupport; public class TypeParserTest { @@ -37,6 +41,17 @@ public class TypeParserTest DatabaseDescriptor.daemonInitialization(); } + @Test + public void testRejectsNonAbstractTypeWithoutInitializing() throws SyntaxException + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + assertThatThrownBy(() -> TypeParser.parse(ClassLoadingTestNonAssignable.class.getName())) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + AbstractType.class.getName()); + + assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)); + } + @Test public void testParse() throws ConfigurationException, SyntaxException { diff --git a/test/unit/org/apache/cassandra/diag/ClassLoadingTestDiagnosticEvent.java b/test/unit/org/apache/cassandra/diag/ClassLoadingTestDiagnosticEvent.java new file mode 100644 index 0000000000..cca9d07aef --- /dev/null +++ b/test/unit/org/apache/cassandra/diag/ClassLoadingTestDiagnosticEvent.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.cassandra.diag; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; + +/** + * A minimal, valid {@link DiagnosticEvent} subclass used by {@code DiagnosticEventPersistenceTest} to confirm that + * the type-checked load path in {@link DiagnosticEventPersistence} resolves a correct subtype. Lives in the + * {@code org.apache.cassandra} namespace so it passes the persistence package-prefix guard. + */ +public final class ClassLoadingTestDiagnosticEvent extends DiagnosticEvent +{ + public enum TestType { TEST } + + @Override + public Enum getType() + { + return TestType.TEST; + } + + @Override + public Map toMap() + { + return Collections.emptyMap(); + } +} diff --git a/test/unit/org/apache/cassandra/diag/DiagnosticEventPersistenceTest.java b/test/unit/org/apache/cassandra/diag/DiagnosticEventPersistenceTest.java new file mode 100644 index 0000000000..9315054d61 --- /dev/null +++ b/test/unit/org/apache/cassandra/diag/DiagnosticEventPersistenceTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.cassandra.diag; + +import java.io.InvalidClassException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.junit.Test; + +import org.apache.cassandra.utils.ClassLoadingTestNonAssignable; +import org.apache.cassandra.utils.ClassLoadingTestSupport; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class DiagnosticEventPersistenceTest +{ + /** + * Reflectively drives the (private) bespoke type-checked load path + * {@link DiagnosticEventPersistence#getEventClass(String)}. + */ + private static Class getEventClass(String eventClazz) throws Throwable + { + Method method = DiagnosticEventPersistence.class.getDeclaredMethod("getEventClass", String.class); + method.setAccessible(true); + try + { + return (Class) method.invoke(DiagnosticEventPersistence.instance(), eventClazz); + } + catch (InvocationTargetException e) + { + throw e.getCause(); + } + } + + @Test + public void testNonDiagnosticEventRejectedWithoutInitializing() throws Throwable + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + + assertThatThrownBy(() -> getEventClass(ClassLoadingTestNonAssignable.class.getName())) + .isInstanceOf(InvalidClassException.class) + .hasMessageContaining("must be of type DiagnosticEvent"); + + assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse(); + } + + @Test + public void testValidDiagnosticEventSubclassResolves() throws Throwable + { + Class resolved = getEventClass(ClassLoadingTestDiagnosticEvent.class.getName()); + assertThat(resolved).isEqualTo(ClassLoadingTestDiagnosticEvent.class); + } +} diff --git a/test/unit/org/apache/cassandra/index/SecondaryIndexManagerTest.java b/test/unit/org/apache/cassandra/index/SecondaryIndexManagerTest.java index d8fb99f40f..29389b84d3 100644 --- a/test/unit/org/apache/cassandra/index/SecondaryIndexManagerTest.java +++ b/test/unit/org/apache/cassandra/index/SecondaryIndexManagerTest.java @@ -38,11 +38,15 @@ import org.apache.cassandra.db.compaction.CompactionInfo; import org.apache.cassandra.db.lifecycle.SSTableSet; import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.notifications.SSTableAddedNotification; +import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.schema.IndexMetadata; +import org.apache.cassandra.utils.ClassLoadingTestNonAssignable; +import org.apache.cassandra.utils.ClassLoadingTestSupport; import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.KillerForTests; import org.apache.cassandra.utils.concurrent.Refs; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -57,6 +61,17 @@ public class SecondaryIndexManagerTest extends CQLTester TestingIndex.clear(); } + @Test + public void rejectsNonIndexClassWithoutInitializing() + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + assertThatThrownBy(() -> SecondaryIndexManager.loadIndexClass(ClassLoadingTestNonAssignable.class.getName())) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + Index.class.getName()); + + assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)); + } + @Test public void creatingIndexMarksTheIndexAsBuilt() throws Throwable { diff --git a/test/unit/org/apache/cassandra/index/sasi/conf/IndexModeTest.java b/test/unit/org/apache/cassandra/index/sasi/conf/IndexModeTest.java index 1cea469520..4669433888 100644 --- a/test/unit/org/apache/cassandra/index/sasi/conf/IndexModeTest.java +++ b/test/unit/org/apache/cassandra/index/sasi/conf/IndexModeTest.java @@ -30,10 +30,16 @@ import org.apache.cassandra.db.marshal.AsciiType; import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.index.sasi.analyzer.AbstractAnalyzer; +import org.apache.cassandra.index.sasi.analyzer.NonTokenizingAnalyzer; import org.apache.cassandra.index.sasi.disk.OnDiskIndexBuilder.Mode; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.ClassLoadingTestNonAssignable; +import org.apache.cassandra.utils.ClassLoadingTestSupport; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class IndexModeTest @@ -184,8 +190,8 @@ public class IndexModeTest { ColumnMetadata cd = ColumnMetadata.regularColumn(cfm, ByteBufferUtil.bytes("TestColumnMetadata"), BytesType.instance); - IndexMode result = IndexMode.getMode(cd, Collections.singletonMap("analyzer_class", "java.lang.Object")); - Assert.assertEquals(Object.class, result.analyzerClass); + IndexMode result = IndexMode.getMode(cd, Collections.singletonMap("analyzer_class", NonTokenizingAnalyzer.class.getName())); + Assert.assertEquals(NonTokenizingAnalyzer.class, result.analyzerClass); Assert.assertTrue(result.isAnalyzed); Assert.assertFalse(result.isLiteral); Assert.assertEquals((long)(1073741824 * 0.15), result.maxCompactionFlushMemoryInBytes); @@ -197,16 +203,55 @@ public class IndexModeTest { ColumnMetadata cd = ColumnMetadata.regularColumn(cfm, ByteBufferUtil.bytes("TestColumnMetadata"), BytesType.instance); - IndexMode result = IndexMode.getMode(cd, ImmutableMap.of("analyzer_class", "java.lang.Object", + IndexMode result = IndexMode.getMode(cd, ImmutableMap.of("analyzer_class", NonTokenizingAnalyzer.class.getName(), "analyzed", "false")); - Assert.assertEquals(Object.class, result.analyzerClass); + Assert.assertEquals(NonTokenizingAnalyzer.class, result.analyzerClass); Assert.assertFalse(result.isAnalyzed); Assert.assertFalse(result.isLiteral); Assert.assertEquals((long)(1073741824 * 0.15), result.maxCompactionFlushMemoryInBytes); Assert.assertEquals(Mode.PREFIX, result.mode); } + @Test + public void test_bytesType_rejectsNonAnalyzerWithoutInitializing() + { + ColumnMetadata cd = ColumnMetadata.regularColumn(cfm, ByteBufferUtil.bytes("TestColumnMetadata"), BytesType.instance); + + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + assertThatThrownBy(() -> IndexMode.getMode(cd, Collections.singletonMap("analyzer_class", ClassLoadingTestNonAssignable.class.getName()))) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + AbstractAnalyzer.class.getName()); + + Assert.assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)); + } + + @Test + public void test_bytesType_missingAnalyzerFallsBack() + { + ColumnMetadata cd = ColumnMetadata.regularColumn(cfm, ByteBufferUtil.bytes("TestColumnMetadata"), BytesType.instance); + + IndexMode result = IndexMode.getMode(cd, Collections.singletonMap("analyzer_class", "does.not.ExistAnalyzer")); + Assert.assertNull(result.analyzerClass); + Assert.assertFalse(result.isAnalyzed); + Assert.assertFalse(result.isLiteral); + Assert.assertEquals((long)(1073741824 * 0.15), result.maxCompactionFlushMemoryInBytes); + Assert.assertEquals(Mode.PREFIX, result.mode); + } + + @Test + public void test_validateAnalyzer_rejectsNonAnalyzerWithoutInitializing() + { + ColumnMetadata cd = ColumnMetadata.regularColumn(cfm, ByteBufferUtil.bytes("TestColumnMetadata"), UTF8Type.instance); + + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + assertThatThrownBy(() -> IndexMode.validateAnalyzer(Collections.singletonMap("analyzer_class", ClassLoadingTestNonAssignable.class.getName()), cd)) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + AbstractAnalyzer.class.getName()); + + Assert.assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)); + } + @Test public void test_bytesType_maxCompactionFlushMemoryInBytes() { diff --git a/test/unit/org/apache/cassandra/schema/CompactionParamsTest.java b/test/unit/org/apache/cassandra/schema/CompactionParamsTest.java new file mode 100644 index 0000000000..3831c2db25 --- /dev/null +++ b/test/unit/org/apache/cassandra/schema/CompactionParamsTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.cassandra.schema; + +import org.junit.Test; + +import org.apache.cassandra.db.compaction.AbstractCompactionStrategy; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.utils.ClassLoadingTestNonAssignable; +import org.apache.cassandra.utils.ClassLoadingTestSupport; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class CompactionParamsTest +{ + @Test + public void testRejectsNonCompactionStrategyWithoutInitializing() + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + + assertThatThrownBy(() -> CompactionParams.classFromName(ClassLoadingTestNonAssignable.class.getName())) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + AbstractCompactionStrategy.class.getName()); + + assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse(); + } +} diff --git a/test/unit/org/apache/cassandra/schema/CompressionParamsTest.java b/test/unit/org/apache/cassandra/schema/CompressionParamsTest.java new file mode 100644 index 0000000000..633b0cc60b --- /dev/null +++ b/test/unit/org/apache/cassandra/schema/CompressionParamsTest.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.cassandra.schema; + +import java.util.Collections; + +import org.junit.Test; + +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.io.compress.ICompressor; +import org.apache.cassandra.utils.ClassLoadingTestNonAssignable; +import org.apache.cassandra.utils.ClassLoadingTestSupport; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class CompressionParamsTest +{ + @Test + public void testRejectsNonCompressorWithoutInitializing() + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + + assertThatThrownBy(() -> CompressionParams.fromMap(Collections.singletonMap(CompressionParams.CLASS, + ClassLoadingTestNonAssignable.class.getName()))) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + ICompressor.class.getName()); + + assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse(); + } +} diff --git a/test/unit/org/apache/cassandra/schema/IndexMetadataTest.java b/test/unit/org/apache/cassandra/schema/IndexMetadataTest.java index 0bcd0dfc28..ef221141d8 100644 --- a/test/unit/org/apache/cassandra/schema/IndexMetadataTest.java +++ b/test/unit/org/apache/cassandra/schema/IndexMetadataTest.java @@ -20,10 +20,21 @@ */ package org.apache.cassandra.schema; +import java.util.Collections; + import org.junit.Assert; import org.junit.Test; import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.statements.schema.IndexTarget; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.index.Index; +import org.apache.cassandra.utils.ClassLoadingTestNonAssignable; +import org.apache.cassandra.utils.ClassLoadingTestSupport; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class IndexMetadataTest { @@ -33,4 +44,24 @@ public class IndexMetadataTest Assert.assertEquals("aB4__idx", IndexMetadata.generateDefaultIndexName("a B-4@!_+")); Assert.assertEquals("34_Ddd_F6_idx", IndexMetadata.generateDefaultIndexName("34_()Ddd", new ColumnIdentifier("#F%6*", true))); } + + @Test + public void testRejectsNonCustomIndexWithoutInitializing() + { + TableMetadata table = TableMetadata.builder("ks", "tbl") + .addPartitionKeyColumn("pk", UTF8Type.instance) + .build(); + IndexMetadata index = IndexMetadata.fromSchemaMetadata("idx", + IndexMetadata.Kind.CUSTOM, + Collections.singletonMap(IndexTarget.CUSTOM_INDEX_OPTION_NAME, + ClassLoadingTestNonAssignable.class.getName())); + + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + + assertThatThrownBy(() -> index.validate(table)) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + Index.class.getName()); + + assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse(); + } } diff --git a/test/unit/org/apache/cassandra/security/CipherFactoryTest.java b/test/unit/org/apache/cassandra/security/CipherFactoryTest.java index 29302b7c2c..2399a0d859 100644 --- a/test/unit/org/apache/cassandra/security/CipherFactoryTest.java +++ b/test/unit/org/apache/cassandra/security/CipherFactoryTest.java @@ -34,12 +34,34 @@ import org.junit.Before; import org.junit.Test; import org.apache.cassandra.config.TransparentDataEncryptionOptions; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.utils.ClassLoadingTestNonAssignable; +import org.apache.cassandra.utils.ClassLoadingTestSupport; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; public class CipherFactoryTest { + @Test + public void keyProviderWrongTypeRejectedWithoutInitializing() + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + + TransparentDataEncryptionOptions options = EncryptionContextGenerator.createEncryptionOptions(); + options.key_provider.class_name = ClassLoadingTestNonAssignable.class.getName(); + + // CipherFactory wraps the load failure; the cause is the type-check ConfigurationException + assertThatThrownBy(() -> new CipherFactory(options)) + .isInstanceOf(RuntimeException.class) + .hasCauseInstanceOf(ConfigurationException.class) + .hasStackTraceContaining("must extend or implement " + KeyProvider.class.getName()); + + assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse(); + } + // http://www.gutenberg.org/files/4300/4300-h/4300-h.htm static final String ULYSSEUS = "Stately, plump Buck Mulligan came from the stairhead, bearing a bowl of lather on which a mirror and a razor lay crossed. " + "A yellow dressinggown, ungirdled, was sustained gently behind him on the mild morning air. He held the bowl aloft and intoned: " + diff --git a/test/unit/org/apache/cassandra/triggers/TriggerExecutorTest.java b/test/unit/org/apache/cassandra/triggers/TriggerExecutorTest.java index 199e6372a1..d295edbdf2 100644 --- a/test/unit/org/apache/cassandra/triggers/TriggerExecutorTest.java +++ b/test/unit/org/apache/cassandra/triggers/TriggerExecutorTest.java @@ -35,9 +35,12 @@ import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.TriggerMetadata; import org.apache.cassandra.schema.Triggers; import org.apache.cassandra.triggers.TriggerExecutorTest.SameKeySameCfTrigger; +import org.apache.cassandra.utils.ClassLoadingTestSupport; import org.apache.cassandra.utils.FBUtilities; import static org.apache.cassandra.utils.ByteBufferUtil.bytes; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -366,4 +369,28 @@ public class TriggerExecutorTest return cmp != 0 ? cmp : m1.key().compareTo(m2.key()); } } + + @Test + public void nonTriggerClassRejectedWithoutInitializing() throws Exception + { + ClassLoadingTestSupport.assertNotInitialized(NonTrigger.class); + + assertThatExceptionOfType(ConfigurationException.class) + .isThrownBy(() -> TriggerExecutor.instance.loadTriggerClass(NonTrigger.class.getName())) + .withMessageContaining("must extend or implement " + ITrigger.class.getName()); + + assertThat(ClassLoadingTestSupport.wasInitialized(NonTrigger.class)).isFalse(); + } + + public static class NonTrigger + { + static + { + ClassLoadingTestSupport.markInitialized(NonTrigger.class); + } + + public NonTrigger() + { + } + } } diff --git a/test/unit/org/apache/cassandra/utils/ClassLoadingTestNonAssignable.java b/test/unit/org/apache/cassandra/utils/ClassLoadingTestNonAssignable.java new file mode 100644 index 0000000000..8faa92286a --- /dev/null +++ b/test/unit/org/apache/cassandra/utils/ClassLoadingTestNonAssignable.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.cassandra.utils; + +public final class ClassLoadingTestNonAssignable +{ + static + { + ClassLoadingTestSupport.markInitialized(ClassLoadingTestNonAssignable.class); + } + + private ClassLoadingTestNonAssignable() + { + } +} diff --git a/test/unit/org/apache/cassandra/utils/ClassLoadingTestSupport.java b/test/unit/org/apache/cassandra/utils/ClassLoadingTestSupport.java new file mode 100644 index 0000000000..41d76473f9 --- /dev/null +++ b/test/unit/org/apache/cassandra/utils/ClassLoadingTestSupport.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.cassandra.utils; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public final class ClassLoadingTestSupport +{ + private static final Set initializedClasses = ConcurrentHashMap.newKeySet(); + + private ClassLoadingTestSupport() + { + } + + public static void markInitialized(Class initializedClass) + { + initializedClasses.add(initializedClass.getName()); + } + + public static boolean wasInitialized(Class initializedClass) + { + return initializedClasses.contains(initializedClass.getName()); + } + + public static void assertNotInitialized(Class initializedClass) + { + if (wasInitialized(initializedClass)) + throw new AssertionError(initializedClass.getName() + " has already been initialized"); + } +} diff --git a/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java b/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java index 833f2e41bf..da73005221 100644 --- a/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java +++ b/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java @@ -23,6 +23,7 @@ import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.TreeMap; @@ -43,14 +44,75 @@ import org.junit.Test; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.dht.*; +import org.apache.cassandra.audit.IAuditLogger; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.exceptions.ConfigurationException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class FBUtilitiesTest { + @Test + public void testTypedClassForNameRejectsWithoutInitializing() + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + assertThatThrownBy(() -> FBUtilities.classForNameWithoutInitialization(ClassLoadingTestNonAssignable.class.getName(), "test class", Runnable.class)) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + Runnable.class.getName()); + + assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)); + } + + @Test + public void testTypedConstructRejectsWithoutInitializing() + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + assertThatThrownBy(() -> FBUtilities.construct(ClassLoadingTestNonAssignable.class.getName(), "test class", Runnable.class)) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + Runnable.class.getName()); + + assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)); + } + + @Test + public void testTypedInstanceOrConstructRejectsWithoutInitializing() + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + assertThatThrownBy(() -> FBUtilities.instanceOrConstruct(ClassLoadingTestNonAssignable.class.getName(), "test class", Runnable.class)) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("must extend or implement " + Runnable.class.getName()); + + assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)); + } + + @Test + public void testNewAuditLoggerRejectsWrongTypeWithoutInitializing() + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + assertThatThrownBy(() -> FBUtilities.newAuditLogger(ClassLoadingTestNonAssignable.class.getName(), Collections.emptyMap())) + .isInstanceOf(ConfigurationException.class) + .hasRootCauseInstanceOf(ConfigurationException.class) + .hasStackTraceContaining("must extend or implement " + IAuditLogger.class.getName()); + + assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)); + } + + @Test + public void testIsAuditLoggerClassExistsRejectsWrongTypeWithoutInitializing() + { + ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class); + assertFalse(FBUtilities.isAuditLoggerClassExists(ClassLoadingTestNonAssignable.class.getName())); + assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)); + + assertTrue(FBUtilities.isAuditLoggerClassExists("NoOpAuditLogger")); + assertFalse(FBUtilities.isAuditLoggerClassExists("does.not.ExistAuditLogger")); + } + @Test public void testCompareByteSubArrays() { diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/OptionReplication.java b/tools/stress/src/org/apache/cassandra/stress/settings/OptionReplication.java index 141c645079..ff2d2ef2cc 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/OptionReplication.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/OptionReplication.java @@ -28,6 +28,7 @@ import java.util.Map; import com.google.common.base.Function; import org.apache.cassandra.locator.AbstractReplicationStrategy; +import org.apache.cassandra.utils.FBUtilities; /** * For specifying replication options @@ -76,9 +77,9 @@ class OptionReplication extends OptionMulti { try { - Class clazz = Class.forName(fullname); - if (!AbstractReplicationStrategy.class.isAssignableFrom(clazz)) - throw new IllegalArgumentException(clazz + " is not a replication strategy"); + FBUtilities.classForNameWithoutInitialization(fullname, + "replication strategy", + AbstractReplicationStrategy.class); strategy = fullname; break; } catch (Exception ignore) diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsMode.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsMode.java index 7e53547e81..e385b12622 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsMode.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsMode.java @@ -31,6 +31,7 @@ import com.datastax.driver.core.PlainTextAuthProvider; import com.datastax.driver.core.ProtocolOptions; import com.datastax.driver.core.ProtocolVersion; import org.apache.cassandra.stress.util.ResultLogger; +import org.apache.cassandra.utils.FBUtilities; public class SettingsMode implements Serializable { @@ -72,9 +73,10 @@ public class SettingsMode implements Serializable { try { - Class clazz = Class.forName(authProviderClassname); - if (!AuthProvider.class.isAssignableFrom(clazz)) - throw new IllegalArgumentException(clazz + " is not a valid auth provider"); + Class clazz = + FBUtilities.classForNameWithoutInitialization(authProviderClassname, + "auth provider", + AuthProvider.class); // check we can instantiate it if (PlainTextAuthProvider.class.equals(clazz)) {