From 4b56616660a24e3b15d35cfb6028ea7d247234ef Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 22 Aug 2026 21:13:10 -0600 Subject: [PATCH] fix: support full null runtimes --- .github/workflows/tests.yml | 5 +++++ system/FrameworkSupertype.cfc | 4 ++-- system/RestHandler.cfc | 14 ++++++------ system/async/tasks/FutureTask.cfc | 6 ++--- system/cache/AbstractCacheBoxProvider.cfc | 6 ++--- system/cache/CacheFactory.cfc | 10 ++++----- system/cache/config/CacheBoxConfig.cfc | 10 ++++----- system/cache/store/ConcurrentStore.cfc | 2 +- system/cache/store/DiskStore.cfc | 4 ++-- system/core/delegates/Env.cfc | 2 +- system/core/delegates/Flow.cfc | 2 +- system/core/util/Util.cfc | 6 ++--- system/ioc/Builder.cfc | 6 ++--- system/logging/LogEvent.cfc | 4 ++-- system/logging/Logger.cfc | 2 +- system/logging/config/LogBoxConfig.cfc | 20 ++++++++--------- .../modules/HTMLHelper/models/HTMLHelper.cfc | 2 +- system/remote/ColdboxProxy.cfc | 10 ++++----- system/testing/BaseTestCase.cfc | 16 +++++++++----- system/testing/VirtualApp.cfc | 4 ++-- system/web/Controller.cfc | 16 +++++++------- system/web/Renderer.cfc | 14 +++++++----- system/web/context/InterceptorBuffer.cfc | 4 ++-- system/web/context/InterceptorState.cfc | 2 +- system/web/context/RequestContext.cfc | 21 ++++++++++++++++-- system/web/flash/AbstractFlashScope.cfc | 2 +- system/web/services/BaseService.cfc | 8 +++---- system/web/services/HandlerService.cfc | 16 +++++++++----- tests/full-null/Application.cfc | 11 ++++++++++ tests/full-null/index.cfm | 22 +++++++++++++++++++ .../config/CacheBoxConfigWithDataCFCTest.cfc | 11 ++++++++++ .../specs/cache/providers/CFProviderTest.cfc | 3 +-- .../cache/providers/LuceeProviderTest.cfc | 2 +- tests/specs/core/delegates/EnvSpec.cfc | 7 ++++++ tests/specs/core/util/UtilTest.cfc | 7 ++++++ .../specs/logging/config/LogBoxConfigTest.cfc | 9 ++++++++ 36 files changed, 197 insertions(+), 93 deletions(-) create mode 100644 tests/full-null/Application.cfc create mode 100644 tests/full-null/index.cfm diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9fddfc449..47e1331f8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -80,6 +80,11 @@ jobs: box server start serverConfigFile="server-${{ matrix.cfengine }}.json" --noSaveSettings --debug curl http://127.0.0.1:8599/test-harness + - name: Run Adobe Full Null Regression + if: ${{ matrix.cfengine == 'adobe@2025' }} + run: | + curl --fail-with-body http://127.0.0.1:8599/tests/full-null/index.cfm + - name: Run Tests run: | box run-script tests diff --git a/system/FrameworkSupertype.cfc b/system/FrameworkSupertype.cfc index 47e697383..a4a676589 100644 --- a/system/FrameworkSupertype.cfc +++ b/system/FrameworkSupertype.cfc @@ -618,7 +618,7 @@ component serializable="false" accessors="true" { * @return coldbox.system.async.AsyncManager */ any function async() cbMethod{ - if ( isNull( variables.asyncManager ) ) { + if ( !structKeyExists( variables, "asyncManager" ) || isNull( variables.asyncManager ) ) { variables.asyncManager = variables.wirebox.getInstance( "asyncManager@coldbox" ); } return variables.asyncManager; @@ -752,7 +752,7 @@ component serializable="false" accessors="true" { * @return coldbox.system.async.time.DateTimeHelper */ DateTimeHelper function getDateTimeHelper(){ - if ( isNull( variables.cbDateTimeHelper ) ) { + if ( !structKeyExists( variables, "cbDateTimeHelper" ) || isNull( variables.cbDateTimeHelper ) ) { variables.cbDateTimeHelper = variables.wirebox.getInstance( "coldbox.system.async.time.DateTimeHelper" ); diff --git a/system/RestHandler.cfc b/system/RestHandler.cfc index 713f592a8..d66bc6c19 100644 --- a/system/RestHandler.cfc +++ b/system/RestHandler.cfc @@ -51,7 +51,7 @@ component extends="EventHandler" { }; structAppend( actionArgs, arguments.eventArguments ); // Incoming Format Detection - if ( !isNull( arguments.rc.format ) ) { + if ( structKeyExists( arguments.rc, "format" ) ) { arguments.prc.response.setFormat( arguments.rc.format ); } // Execute action @@ -119,7 +119,7 @@ component extends="EventHandler" { // marshalling below and the header flush further down would be write-after-commit // against either, so bail out entirely. if ( arguments.event.isSSE() || arguments.event.isNoExecution() ) { - if ( !isNull( local.actionResults ) ) { + if ( structKeyExists( local, "actionResults" ) && !isNull( local.actionResults ) ) { return local.actionResults; } return; @@ -127,7 +127,7 @@ component extends="EventHandler" { // Did the controllers set a view to be rendered? If not use renderdata, else just delegate to view. if ( - isNull( local.actionResults ) + ( !structKeyExists( local, "actionResults" ) || isNull( local.actionResults ) ) AND !arguments.event.getCurrentView().len() AND @@ -161,7 +161,7 @@ component extends="EventHandler" { } // If results detected, just return them, controllers requesting to return results - if ( !isNull( local.actionResults ) ) { + if ( structKeyExists( local, "actionResults" ) && !isNull( local.actionResults ) ) { return local.actionResults; } } @@ -186,7 +186,7 @@ component extends="EventHandler" { ){ // Try to discover exception, if not, hard error if ( - !isNull( arguments.prc.exception ) && ( + structKeyExists( arguments.prc, "exception" ) && ( isNull( arguments.exception ) || structIsEmpty( arguments.exception ) ) ) { @@ -415,7 +415,7 @@ component extends="EventHandler" { // case when the a jwt token was valid, but expired if ( - !isNull( arguments.prc.cbSecurity_validatorResults ) && + structKeyExists( arguments.prc, "cbSecurity_validatorResults" ) && arguments.prc.cbSecurity_validatorResults.messages CONTAINS "expired" ) { arguments.event @@ -480,7 +480,7 @@ component extends="EventHandler" { .addMessage( "You are not allowed to access this resource" ); // Check for validator results - if ( !isNull( arguments.prc.cbSecurity_validatorResults ) ) { + if ( structKeyExists( arguments.prc, "cbSecurity_validatorResults" ) ) { arguments.prc.response.addMessage( arguments.prc.cbSecurity_validatorResults.messages ); } diff --git a/system/async/tasks/FutureTask.cfc b/system/async/tasks/FutureTask.cfc index ed266d27f..89ffa0b3f 100644 --- a/system/async/tasks/FutureTask.cfc +++ b/system/async/tasks/FutureTask.cfc @@ -21,7 +21,7 @@ component accessors="true" { * @native The native Future class we are wrapping */ FutureTask function init( native ){ - if ( isNull( arguments.native ) ) { + if ( !structKeyExists( arguments, "native" ) || isNull( arguments.native ) ) { arguments.native = createObject( "java", "java.util.concurrent.FutureTask" ); } variables.native = arguments.native; @@ -70,12 +70,12 @@ component accessors="true" { } // If we have results, return them - if ( !isNull( local.results ) ) { + if ( structKeyExists( local, "results" ) && !isNull( local.results ) ) { return local.results; } // If we didn't, do we have a default value - if ( !isNull( arguments.defaultValue ) ) { + if ( structKeyExists( arguments, "defaultValue" ) && !isNull( arguments.defaultValue ) ) { return arguments.defaultValue; } // Else return null diff --git a/system/cache/AbstractCacheBoxProvider.cfc b/system/cache/AbstractCacheBoxProvider.cfc index ae0ff6ddd..d6b34dd60 100644 --- a/system/cache/AbstractCacheBoxProvider.cfc +++ b/system/cache/AbstractCacheBoxProvider.cfc @@ -514,7 +514,7 @@ component * @return coldbox.system.core.util.Util */ function getUtility(){ - if ( isNull( variables.utility ) ) { + if ( !structKeyExists( variables, "utility" ) || isNull( variables.utility ) ) { variables.utility = new coldbox.system.core.util.Util(); } return variables.utility; @@ -722,7 +722,7 @@ component */ function randomUUID(){ // our UUID creation helper - if ( isNull( variables.uuidHelper ) ) { + if ( !structKeyExists( variables, "uuidHelper" ) || isNull( variables.uuidHelper ) ) { variables.uuidHelper = createObject( "java", "java.util.UUID" ); } return variables.uuidHelper.randomUUID(); @@ -776,7 +776,7 @@ component // Validate configuration values, if they don't exist, then default them to DEFAULTS for ( var key in variables.DEFAULTS ) { - if ( NOT len( variables.configuration[ key ] ) ) { + if ( isNull( variables.configuration[ key ] ) || NOT len( variables.configuration[ key ] ) ) { variables.configuration[ key ] = variables.DEFAULTS[ key ]; } } diff --git a/system/cache/CacheFactory.cfc b/system/cache/CacheFactory.cfc index 059498386..bf4133525 100644 --- a/system/cache/CacheFactory.cfc +++ b/system/cache/CacheFactory.cfc @@ -359,7 +359,7 @@ component accessors=true serializable=false { */ CacheFactory function shutdown(){ // Log startup - if ( !isNull( variables.log ) ) { + if ( isObject( variables.log ) ) { if ( variables.log.canDebug() ) { variables.log.debug( "Shutdown of cache factory: #getFactoryID()# requested and started." ); } @@ -376,7 +376,7 @@ component accessors=true serializable=false { var cache = getCache( item ); // Log it - if ( !isNull( variables.log ) ) { + if ( isObject( variables.log ) ) { if ( variables.log.canDebug() ) { variables.log.debug( "Shutting down cache: #item# on factoryID: #getFactoryID()#." ); } @@ -392,7 +392,7 @@ component accessors=true serializable=false { variables.eventManager.announce( "afterCacheShutdown", { cache : cache } ); // log - if ( !isNull( variables.log ) ) { + if ( isObject( variables.log ) ) { if ( variables.log.canDebug() ) { variables.log.debug( "Cache: #item# was shut down on factoryID: #getFactoryID()#." ); } @@ -423,7 +423,7 @@ component accessors=true serializable=false { } // Log shutdown complete - if ( !isNull( variables.log ) ) { + if ( isObject( variables.log ) ) { if ( variables.log.canDebug() ) { variables.log.debug( "Shutdown of cache factory: #getFactoryID()# completed." ); } @@ -490,7 +490,7 @@ component accessors=true serializable=false { * Remove the cache factory from scope registration if enabled, else does nothing */ CacheFactory function removeFromScope(){ - if ( isNull( variables.config ) ) { + if ( !structKeyExists( variables, "config" ) || isNull( variables.config ) ) { return this; } diff --git a/system/cache/config/CacheBoxConfig.cfc b/system/cache/config/CacheBoxConfig.cfc index 1e3d5e91c..0a4de00f5 100644 --- a/system/cache/config/CacheBoxConfig.cfc +++ b/system/cache/config/CacheBoxConfig.cfc @@ -91,7 +91,7 @@ component accessors="true" { var cacheBoxDSL = arguments.rawDSL; // Is default configuration defined - if ( isNull( cacheBoxDSL.defaultCache ) ) { + if ( !structKeyExists( cacheBoxDSL, "defaultCache" ) || isNull( cacheBoxDSL.defaultCache ) ) { throw( "No default cache defined", "Please define the 'defaultCache'", @@ -104,17 +104,17 @@ component accessors="true" { // Register LogBox Configuration this.logBoxConfig( variables.DEFAULTS.logBoxConfig ); - if ( !isNull( cacheBoxDSL.logBoxConfig ) ) { + if ( structKeyExists( cacheBoxDSL, "logBoxConfig" ) && !isNull( cacheBoxDSL.logBoxConfig ) ) { this.logBoxConfig( cacheBoxDSL.logBoxConfig ); } // Register Server Scope Registration - if ( !isNull( cacheBoxDSL.scopeRegistration ) ) { + if ( structKeyExists( cacheBoxDSL, "scopeRegistration" ) && !isNull( cacheBoxDSL.scopeRegistration ) ) { this.scopeRegistration( argumentCollection = cacheBoxDSL.scopeRegistration ); } // Register Caches - if ( !isNull( cacheBoxDSL.caches ) ) { + if ( structKeyExists( cacheBoxDSL, "caches" ) && !isNull( cacheBoxDSL.caches ) ) { for ( var key in cacheBoxDSL.caches ) { cacheBoxDSL.caches[ key ].name = key; this.cache( argumentCollection = cacheBoxDSL.caches[ key ] ); @@ -122,7 +122,7 @@ component accessors="true" { } // Register listeners - if ( !isNull( cacheBoxDSL.listeners ) ) { + if ( structKeyExists( cacheBoxDSL, "listeners" ) && !isNull( cacheBoxDSL.listeners ) ) { for ( var key in cacheBoxDSL.listeners ) { this.listener( argumentCollection = key ); } diff --git a/system/cache/store/ConcurrentStore.cfc b/system/cache/store/ConcurrentStore.cfc index 13613a4d0..d3915436b 100644 --- a/system/cache/store/ConcurrentStore.cfc +++ b/system/cache/store/ConcurrentStore.cfc @@ -258,7 +258,7 @@ component implements="coldbox.system.cache.store.IObjectStore" accessors="true" * @return java.util.Collections */ private function getJavaCollections(){ - if ( isNull( variables.collections ) ) { + if ( !structKeyExists( variables, "collections" ) || isNull( variables.collections ) ) { variables.collections = createObject( "java", "java.util.Collections" ); } return variables.collections; diff --git a/system/cache/store/DiskStore.cfc b/system/cache/store/DiskStore.cfc index 792c721a6..a6ff83994 100644 --- a/system/cache/store/DiskStore.cfc +++ b/system/cache/store/DiskStore.cfc @@ -46,12 +46,12 @@ component implements="coldbox.system.cache.store.IObjectStore" accessors="true" // Get extra configuration details from cacheProvider's configuration for this diskstore // Auto Expand - if ( isNull( config.autoExpandPath ) ) { + if ( !structKeyExists( config, "autoExpandPath" ) || isNull( config.autoExpandPath ) ) { config.autoExpandPath = true; } // Check directory path - if ( isNull( config.directoryPath ) ) { + if ( !structKeyExists( config, "directoryPath" ) || isNull( config.directoryPath ) ) { throw( message = "The 'directoryPath' configuration property was not found in the cache configuration", detail = "Please check the cache configuration and add the 'directoryPath' property. Current Configuration: #config.toString()#", diff --git a/system/core/delegates/Env.cfc b/system/core/delegates/Env.cfc index 9afb6d453..78bbb4754 100644 --- a/system/core/delegates/Env.cfc +++ b/system/core/delegates/Env.cfc @@ -84,7 +84,7 @@ component singleton { * Retrieve an instance of Java System */ function getJavaSystem(){ - if ( isNull( variables.javaSystem ) ) { + if ( !structKeyExists( variables, "javaSystem" ) || isNull( variables.javaSystem ) ) { variables.javaSystem = createObject( "java", "java.lang.System" ); } return variables.javaSystem; diff --git a/system/core/delegates/Flow.cfc b/system/core/delegates/Flow.cfc index 603526a6a..bb4231c6a 100644 --- a/system/core/delegates/Flow.cfc +++ b/system/core/delegates/Flow.cfc @@ -10,7 +10,7 @@ component accessors=true { * Pivots if used in delegate or normal mode. */ private function getParent(){ - return isNull( $parent ) ? this : $parent; + return structKeyExists( variables, "$parent" ) && !isNull( variables.$parent ) ? variables.$parent : this; } /** diff --git a/system/core/util/Util.cfc b/system/core/util/Util.cfc index 2d582447e..0f542c3ff 100644 --- a/system/core/util/Util.cfc +++ b/system/core/util/Util.cfc @@ -9,7 +9,7 @@ component { private function getClassMappingHelper(){ // Lazy load the helper - if ( isNull( variables.classMappingHelper ) ) { + if ( !structKeyExists( variables, "classMappingHelper" ) || isNull( variables.classMappingHelper ) ) { if ( server.keyExists( "boxlang" ) ) { variables.classMappingHelper = new BoxLangMappingHelper(); } else if ( listFindNoCase( "Lucee", server.coldfusion.productname ) ) { @@ -121,7 +121,7 @@ component { * @return java.net.InetAddress */ private function getInetAddress(){ - if ( isNull( variables.inetAddress ) ) { + if ( !structKeyExists( variables, "inetAddress" ) || isNull( variables.inetAddress ) ) { variables.inetAddress = createObject( "java", "java.net.InetAddress" ); } return variables.inetAddress; @@ -297,7 +297,7 @@ component { * @return coldbox.system.core.dynamic.MixerUtil */ function getMixerUtil(){ - if ( isNull( variables.mixerUtil ) ) { + if ( !structKeyExists( variables, "mixerUtil" ) || isNull( variables.mixerUtil ) ) { variables.mixerUtil = new coldbox.system.core.dynamic.MixerUtil(); } return variables.mixerUtil; diff --git a/system/ioc/Builder.cfc b/system/ioc/Builder.cfc index 1671f7a23..da18d5541 100644 --- a/system/ioc/Builder.cfc +++ b/system/ioc/Builder.cfc @@ -79,7 +79,7 @@ component serializable="false" accessors="true" { * @return coldbox.system.ioc.dsl.ColdBoxDSL */ function getColdBoxDSL(){ - if ( isNull( variables.coldboxDSL ) ) { + if ( !structKeyExists( variables, "coldboxDSL" ) || isNull( variables.coldboxDSL ) ) { variables.coldboxDSL = new coldbox.system.ioc.dsl.ColdBoxDSL( variables.injector ); } return variables.coldboxDSL; @@ -91,7 +91,7 @@ component serializable="false" accessors="true" { * @return coldbox.system.ioc.dsl.CacheBoxDSL */ function getCacheBoxDSL(){ - if ( isNull( variables.cacheBoxDSL ) ) { + if ( !structKeyExists( variables, "cacheBoxDSL" ) || isNull( variables.cacheBoxDSL ) ) { variables.cacheBoxDSL = new coldbox.system.ioc.dsl.CacheBoxDSL( variables.injector ); } return variables.cacheBoxDSL; @@ -103,7 +103,7 @@ component serializable="false" accessors="true" { * @return coldbox.system.ioc.dsl.LogBoxDSL */ function getLogBoxDSL(){ - if ( isNull( variables.logBoxDSL ) ) { + if ( !structKeyExists( variables, "logBoxDSL" ) || isNull( variables.logBoxDSL ) ) { variables.logBoxDSL = new coldbox.system.ioc.dsl.LogBoxDSL( variables.injector ); } return variables.logBoxDSL; diff --git a/system/logging/LogEvent.cfc b/system/logging/LogEvent.cfc index cf2f6766b..f29005c76 100644 --- a/system/logging/LogEvent.cfc +++ b/system/logging/LogEvent.cfc @@ -68,14 +68,14 @@ component accessors="true" { } function getXmlConverter(){ - if ( isNull( variables.xmlConverter ) ) { + if ( !structKeyExists( variables, "xmlConverter" ) || isNull( variables.xmlConverter ) ) { variables.xmlConverter = new coldbox.system.core.conversion.XMLConverter(); } return variables.xmlConverter; } function getUtil(){ - if ( isNull( variables.util ) ) { + if ( !structKeyExists( variables, "util" ) || isNull( variables.util ) ) { variables.util = new coldbox.system.core.util.Util(); } return variables.util; diff --git a/system/logging/Logger.cfc b/system/logging/Logger.cfc index 6648bb3da..7d84f0cc7 100644 --- a/system/logging/Logger.cfc +++ b/system/logging/Logger.cfc @@ -410,7 +410,7 @@ component accessors="true" { thisAppender.logMessage( thread.logEvent ); } } else { - if ( isNull( local.logEvent ) ) { + if ( !structKeyExists( local, "logEvent" ) || isNull( local.logEvent ) ) { var logEvent = new coldbox.system.logging.LogEvent( argumentCollection = arguments ); } thisAppender.logMessage( local.logEvent ); diff --git a/system/logging/config/LogBoxConfig.cfc b/system/logging/config/LogBoxConfig.cfc index 78db4182b..b3490cf0d 100644 --- a/system/logging/config/LogBoxConfig.cfc +++ b/system/logging/config/LogBoxConfig.cfc @@ -69,7 +69,7 @@ component accessors="true" { * Get the ColdBox Utility object */ private function getUtil(){ - if ( isNull( variables.utility ) ) { + if ( !structKeyExists( variables, "utility" ) || isNull( variables.utility ) ) { variables.utility = new coldbox.system.core.util.Util(); } return variables.utility; @@ -103,13 +103,13 @@ component accessors="true" { } // Register Root Logger - if ( isNull( logBoxDSL.root ) ) { + if ( !structKeyExists( logBoxDSL, "root" ) || isNull( logBoxDSL.root ) ) { logBoxDSL.root = { appenders : "*" }; } root( argumentCollection = logBoxDSL.root ); // Register Categories - if ( !isNull( logBoxDSL.categories ) ) { + if ( structKeyExists( logBoxDSL, "categories" ) && !isNull( logBoxDSL.categories ) ) { for ( var key in logBoxDSL.categories ) { logBoxDSL.categories[ key ].name = key; category( argumentCollection = logBoxDSL.categories[ key ] ); @@ -117,27 +117,27 @@ component accessors="true" { } // Register Level Categories - if ( !isNull( logBoxDSL.debug ) ) { + if ( structKeyExists( logBoxDSL, "debug" ) && !isNull( logBoxDSL.debug ) ) { DEBUG( argumentCollection = getUtil().arrayToStruct( logBoxDSL.debug ) ); } - if ( !isNull( logBoxDSL.info ) ) { + if ( structKeyExists( logBoxDSL, "info" ) && !isNull( logBoxDSL.info ) ) { INFO( argumentCollection = getUtil().arrayToStruct( logBoxDSL.info ) ); } - if ( !isNull( logBoxDSL.warn ) ) { + if ( structKeyExists( logBoxDSL, "warn" ) && !isNull( logBoxDSL.warn ) ) { WARN( argumentCollection = getUtil().arrayToStruct( logBoxDSL.warn ) ); } - if ( !isNull( logBoxDSL.error ) ) { + if ( structKeyExists( logBoxDSL, "error" ) && !isNull( logBoxDSL.error ) ) { ERROR( argumentCollection = getUtil().arrayToStruct( logBoxDSL.error ) ); } - if ( !isNull( logBoxDSL.fatal ) ) { + if ( structKeyExists( logBoxDSL, "fatal" ) && !isNull( logBoxDSL.fatal ) ) { FATAL( argumentCollection = getUtil().arrayToStruct( logBoxDSL.fatal ) ); } - if ( !isNull( logBoxDSL.off ) ) { + if ( structKeyExists( logBoxDSL, "off" ) && !isNull( logBoxDSL.off ) ) { OFF( argumentCollection = getUtil().arrayToStruct( logBoxDSL.off ) ); } // Register serializeExtraInfo - if ( !isNull( logBoxDSL.serializeExtraInfo ) ) { + if ( structKeyExists( logBoxDSL, "serializeExtraInfo" ) && !isNull( logBoxDSL.serializeExtraInfo ) ) { variables.serializeExtraInfo = logBoxDSL.serializeExtraInfo; } diff --git a/system/modules/HTMLHelper/models/HTMLHelper.cfc b/system/modules/HTMLHelper/models/HTMLHelper.cfc index 3fa9fcc6b..b93a3944c 100755 --- a/system/modules/HTMLHelper/models/HTMLHelper.cfc +++ b/system/modules/HTMLHelper/models/HTMLHelper.cfc @@ -266,7 +266,7 @@ component // Check if we have a base URL and if we need to build our link if ( arguments.noBaseURL eq FALSE and NOT find( "://", arguments.href ) ) { // Verify SSL Bit - if ( isNull( arguments.ssl ) ) { + if ( !structKeyExists( arguments, "ssl" ) || isNull( arguments.ssl ) ) { arguments.ssl = event.isSSL(); } // Build it diff --git a/system/remote/ColdboxProxy.cfc b/system/remote/ColdboxProxy.cfc index 5f0f7bb49..8cb9dc0bc 100644 --- a/system/remote/ColdboxProxy.cfc +++ b/system/remote/ColdboxProxy.cfc @@ -28,7 +28,7 @@ component serializable="false" accessors="true" { * Get the ColdBox app key used in the application scope. */ private function getColdboxAppKey(){ - if ( !isNull( application.cbBootstrap ) ) { + if ( structKeyExists( application, "cbBootstrap" ) && !isNull( application.cbBootstrap ) ) { return application.cbBootstrap.getCOLDBOX_APP_KEY(); } return "cbController"; @@ -117,7 +117,7 @@ component serializable="false" accessors="true" { } // Return results from handler only if found, else method will produce a null result - if ( !isNull( local.refLocal.results ) ) { + if ( structKeyExists( local.refLocal, "results" ) && !isNull( local.refLocal.results ) ) { // preProxyResults interception call cbController.getInterceptorService().announce( "preProxyResults", { "proxyResults" : local.refLocal } ); @@ -135,7 +135,7 @@ component serializable="false" accessors="true" { private boolean function announce( required state, struct data = {} ){ try { // Backwards Compat: Remove by ColdBox 7 - if ( !isNull( arguments.interceptData ) ) { + if ( structKeyExists( arguments, "interceptData" ) && !isNull( arguments.interceptData ) ) { arguments.data = arguments.interceptData; } getController().getInterceptorService().announce( arguments.state, arguments.data ); @@ -339,7 +339,7 @@ component serializable="false" accessors="true" { * @return coldbox.system.core.util.Util */ private any function getUtil(){ - if ( isNull( variables.util ) ) { + if ( !structKeyExists( variables, "util" ) || isNull( variables.util ) ) { variables.util = new coldbox.system.core.util.Util(); } return variables.util; @@ -353,7 +353,7 @@ component serializable="false" accessors="true" { * @return coldbox.system.remote.RemotingUtil */ private function getRemotingUtil(){ - if ( isNull( variables.remotingUtil ) ) { + if ( !structKeyExists( variables, "remotingUtil" ) || isNull( variables.remotingUtil ) ) { variables.remotingUtil = new coldbox.system.remote.RemotingUtil(); } return variables.remotingUtil; diff --git a/system/testing/BaseTestCase.cfc b/system/testing/BaseTestCase.cfc index e5c3056a9..27e966d72 100755 --- a/system/testing/BaseTestCase.cfc +++ b/system/testing/BaseTestCase.cfc @@ -159,7 +159,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { * BDD: The main setup method for running ColdBox Integration enabled tests */ function beforeAll(){ - if ( isNull( variables._ranBeforeAll ) ) { + if ( !structKeyExists( variables, "_ranBeforeAll" ) || isNull( variables._ranBeforeAll ) ) { beforeTests(); variables._ranBeforeAll = true; } @@ -169,7 +169,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { * BDD: The main teardown for ColdBox enabled applications after all tests execute */ function afterAll(){ - if ( isNull( variables._ranAfterAll ) ) { + if ( !structKeyExists( variables, "_ranAfterAll" ) || isNull( variables._ranAfterAll ) ) { afterTests(); variables._ranAfterAll = true; } @@ -405,6 +405,9 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { try { // Make sure our routing service can be manipulated prepareMock( routingService ) + .$( "getCGIElement" ) + .$args( "path_info", requestContext ) + .$results( "" ) .$( "getCGIElement" ) .$args( "script_name", requestContext ) .$results( "" ) @@ -437,6 +440,9 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { requestContext.collectionAppend( routeParts.queryStringCollection ); // mock the cleaned paths so SES routes will be recognized prepareMock( routingService ) + .$( "getCGIElement" ) + .$args( "path_info", requestContext ) + .$results( "" ) .$( "getCGIElement" ) .$args( "path_info", requestContext ) .$results( routeParts.route ); @@ -804,7 +810,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { numeric asyncJoinTimeout = 0 ){ // Backwards Compat: Remove by ColdBox 7 - if ( !isNull( arguments.interceptData ) ) { + if ( structKeyExists( arguments, "interceptData" ) && !isNull( arguments.interceptData ) ) { arguments.data = arguments.interceptData; } return getController().getInterceptorService().announce( argumentCollection = arguments ); @@ -867,7 +873,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { * @return coldbox.system.core.util.Util */ function getUtil(){ - if ( isNull( variables.cbUtil ) ) { + if ( !structKeyExists( variables, "cbUtil" ) || isNull( variables.cbUtil ) ) { variables.cbUtil = new coldbox.system.core.util.Util(); } return variables.cbUtil; @@ -879,7 +885,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { * @return coldbox.system.core.delegates.Env */ function getEnv(){ - if ( isNull( variables.env ) ) { + if ( !structKeyExists( variables, "env" ) || isNull( variables.env ) ) { variables.env = new coldbox.system.core.delegates.Env(); } return variables.env; diff --git a/system/testing/VirtualApp.cfc b/system/testing/VirtualApp.cfc index 551a98319..f43c99002 100644 --- a/system/testing/VirtualApp.cfc +++ b/system/testing/VirtualApp.cfc @@ -91,7 +91,7 @@ component accessors="true" { * Verifies if the ColdBox application is in application scope and running */ boolean function isRunning(){ - return !isNull( application.cbController ); + return structKeyExists( application, "cbController" ) && !isNull( application.cbController ); } /** @@ -109,7 +109,7 @@ component accessors="true" { * @force If true, it forces all shutdowns this is usually true when doing reinits. Defaults to true for testing. */ function shutdown( boolean force = true ){ - if ( !isNull( application.cbController ) ) { + if ( structKeyExists( application, "cbController" ) && !isNull( application.cbController ) ) { application.cbController.getLoaderService().processShutdown( force = arguments.force ); } structDelete( application, "cbController" ); diff --git a/system/web/Controller.cfc b/system/web/Controller.cfc index b3457b555..ee18cd06c 100644 --- a/system/web/Controller.cfc +++ b/system/web/Controller.cfc @@ -460,10 +460,10 @@ component serializable="false" accessors="true" { var routeString = 0; // Determine relocation type - if ( !isNull( arguments.url ) && len( arguments.url ) ) { + if ( structKeyExists( arguments, "url" ) && !isNull( arguments.url ) && len( arguments.url ) ) { relocationType = "URL"; } - if ( !isNull( arguments.URI ) && len( arguments.URI ) ) { + if ( structKeyExists( arguments, "URI" ) && !isNull( arguments.URI ) && len( arguments.URI ) ) { relocationType = "URI"; } @@ -493,7 +493,7 @@ component serializable="false" accessors="true" { case "URL": { relocationURL = arguments.URL; // Check SSL? - if ( !isNull( arguments.ssl ) ) { + if ( structKeyExists( arguments, "ssl" ) && !isNull( arguments.ssl ) ) { relocationURL = updateSSL( relocationURL, arguments.ssl ); } // Query String? @@ -551,7 +551,7 @@ component serializable="false" accessors="true" { relocationURL = relocationURL & "/"; } // Check SSL? - if ( !isNull( arguments.ssl ) ) { + if ( structKeyExists( arguments, "ssl" ) && !isNull( arguments.ssl ) ) { relocationURL = updateSSL( relocationURL, arguments.ssl ); } @@ -725,7 +725,7 @@ component serializable="false" accessors="true" { // Do we have an object coming back? if ( - !isNull( local.results.data ) && + structKeyExists( local.results, "data" ) && isObject( local.results.data ) ) { // Verify $renderdata method convention @@ -740,7 +740,7 @@ component serializable="false" accessors="true" { // Do we need to do action renderings? if ( - !isNull( local.results.data ) && + structKeyExists( local.results, "data" ) && local.results.ehBean.getActionMetadata( "renderdata", "html" ) neq "html" ) { // Do action Rendering @@ -753,7 +753,7 @@ component serializable="false" accessors="true" { } // Are we caching - if ( isCachingOn && !isNull( local.results.data ) ) { + if ( isCachingOn && structKeyExists( local.results, "data" ) ) { oCache.set( objectKey = cacheKey, object = local.results.data, @@ -763,7 +763,7 @@ component serializable="false" accessors="true" { } // Are we returning data? - if ( !isNull( local.results.data ) ) { + if ( structKeyExists( local.results, "data" ) ) { return local.results.data; } } diff --git a/system/web/Renderer.cfc b/system/web/Renderer.cfc index 4673adca7..72d910b29 100755 --- a/system/web/Renderer.cfc +++ b/system/web/Renderer.cfc @@ -445,11 +445,15 @@ component viewPath = arguments.viewPath, viewHelperPath = arguments.viewHelperPath, args = arguments.args, - rendererVariables = ( isNull( attributes.rendererVariables ) ? variables : attributes.rendererVariables ), - event = event, - rc = event.getCollection(), - prc = event.getPrivateCollection(), - viewVariables = arguments.viewVariables + rendererVariables = ( + isDefined( "attributes.rendererVariables" ) && !isNull( attributes.rendererVariables ) + ? attributes.rendererVariables + : variables + ), + event = event, + rc = event.getCollection(), + prc = event.getPrivateCollection(), + viewVariables = arguments.viewVariables ); } diff --git a/system/web/context/InterceptorBuffer.cfc b/system/web/context/InterceptorBuffer.cfc index 9ef62ddfb..6d7ec1526 100644 --- a/system/web/context/InterceptorBuffer.cfc +++ b/system/web/context/InterceptorBuffer.cfc @@ -17,7 +17,7 @@ component accessors="false" { * Get the underlying string builder, creating it only when output is produced. */ function get(){ - if ( isNull( variables.builder ) ) { + if ( !structKeyExists( variables, "builder" ) || isNull( variables.builder ) ) { variables.builder = createObject( "java", "java.lang.StringBuilder" ).init( "" ) } @@ -62,7 +62,7 @@ component accessors="false" { * Check if the underlying builder has been created. */ boolean function hasContent(){ - return !isNull( variables.builder ) + return structKeyExists( variables, "builder" ) && !isNull( variables.builder ) } /** diff --git a/system/web/context/InterceptorState.cfc b/system/web/context/InterceptorState.cfc index 155507d68..81ec75b59 100644 --- a/system/web/context/InterceptorState.cfc +++ b/system/web/context/InterceptorState.cfc @@ -620,7 +620,7 @@ component accessors="true" extends="coldbox.system.core.events.EventPool" { * Get the service logger */ function getLogger(){ - if ( isNull( variables.log ) ) { + if ( !structKeyExists( variables, "log" ) || isNull( variables.log ) ) { variables.log = variables.controller.getLogBox().getLogger( this ) } return variables.log diff --git a/system/web/context/RequestContext.cfc b/system/web/context/RequestContext.cfc index 18ff0829a..009909e80 100644 --- a/system/web/context/RequestContext.cfc +++ b/system/web/context/RequestContext.cfc @@ -1552,7 +1552,7 @@ component serializable="false" accessors="true" { * @return coldbox.system.web.context.Response */ function getResponse(){ - if ( isNull( variables.privateContext.response ) ) { + if ( !structKeyExists( variables.privateContext, "response" ) || isNull( variables.privateContext.response ) ) { variables.privateContext.response = new coldbox.system.web.context.Response(); } return variables.privateContext.response; @@ -1901,7 +1901,7 @@ component serializable="false" accessors="true" { !len( getHTTPHeader( "If-None-Match", "" ) ) && len( since ) && isDate( since ) && - parseDateTime( since ) >= arguments.value + isHTTPDateAtOrAfter( since, arguments.value ) ) { noExecution(); setHTTPHeader( statusCode = 304 ); @@ -1910,6 +1910,23 @@ component serializable="false" accessors="true" { return false; } + /** + * Compare an RFC 7231 HTTP date to a CFML date without relying on the runtime's + * timezone-dependent `parseDateTime()` handling of the trailing GMT zone. + */ + private boolean function isHTTPDateAtOrAfter( required string httpDate, required date value ){ + try { + var formatter = createObject( "java", "java.time.format.DateTimeFormatter" ).RFC_1123_DATE_TIME; + var parsed = createObject( "java", "java.time.ZonedDateTime" ) + .parse( javacast( "string", arguments.httpDate ), formatter ) + .toInstant(); + + return parsed.getEpochSecond() >= arguments.value.toInstant().getEpochSecond(); + } catch ( any e ) { + return false; + } + } + /** * Sets the Cache-Control response header from a directive struct. * diff --git a/system/web/flash/AbstractFlashScope.cfc b/system/web/flash/AbstractFlashScope.cfc index 493401424..b8329e36c 100644 --- a/system/web/flash/AbstractFlashScope.cfc +++ b/system/web/flash/AbstractFlashScope.cfc @@ -144,7 +144,7 @@ component accessors="true" { getFlash() // Process only keys that are marked as keep and content exists .filter( function( key, value ){ - return arguments.value.keep && !isNull( arguments.value.content ); + return arguments.value.keep && structKeyExists( arguments.value, "content" ); } ) .each( function( key, value ){ // Inflate into RC? diff --git a/system/web/services/BaseService.cfc b/system/web/services/BaseService.cfc index 5b8fffa38..082684e13 100755 --- a/system/web/services/BaseService.cfc +++ b/system/web/services/BaseService.cfc @@ -45,7 +45,7 @@ component accessors="true" { * Get the service logger */ function getLogger(){ - if ( isNull( variables.log ) ) { + if ( !structKeyExists( variables, "log" ) || isNull( variables.log ) ) { variables.log = variables.controller.getLogBox().getLogger( this ) } return variables.log @@ -55,7 +55,7 @@ component accessors="true" { * Get the Env delegate */ function getEnvDelegate(){ - if ( isNull( variables.envDelegate ) ) { + if ( !structKeyExists( variables, "envDelegate" ) || isNull( variables.envDelegate ) ) { variables.envDelegate = variables.controller.getWireBox().getInstance( "Env@coreDelegates" ) } return variables.envDelegate @@ -65,7 +65,7 @@ component accessors="true" { * Get the LogBox instance (lazy-cached) */ function getLogBox(){ - if ( isNull( variables.logBox ) ) { + if ( !structKeyExists( variables, "logBox" ) || isNull( variables.logBox ) ) { variables.logBox = variables.controller.getLogBox() } return variables.logBox @@ -75,7 +75,7 @@ component accessors="true" { * Get the CacheBox instance (lazy-cached) */ function getCacheBox(){ - if ( isNull( variables.cacheBox ) ) { + if ( !structKeyExists( variables, "cacheBox" ) || isNull( variables.cacheBox ) ) { variables.cacheBox = variables.controller.getCacheBox() } return variables.cacheBox diff --git a/system/web/services/HandlerService.cfc b/system/web/services/HandlerService.cfc index 2bf2454a8..867d213b1 100644 --- a/system/web/services/HandlerService.cfc +++ b/system/web/services/HandlerService.cfc @@ -579,7 +579,7 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { arguments.requestContext.getCurrentRouteRecord(), arguments.requestContext ); - if ( !isNull( routeCacheEntry ) ) { + if ( structKeyExists( local, "routeCacheEntry" ) && !isNull( local.routeCacheEntry ) ) { return routeCacheEntry; } @@ -875,7 +875,7 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { arguments.requestContext.getCurrentRouteRecord(), arguments.requestContext ); - if ( !isNull( routeCacheEntry ) ) { + if ( structKeyExists( local, "routeCacheEntry" ) && !isNull( local.routeCacheEntry ) ) { return routeCacheEntry; } @@ -922,7 +922,9 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { // entry is memoized for the life of the app, and a request-time value // (locale, session, slug) would freeze into every later request's cache // key. resolveCacheSuffix() evaluates it on every read instead. - mdEntry.suffix = arguments.oEventHandler.EVENT_CACHE_SUFFIX; + mdEntry.suffix = structKeyExists( arguments.oEventHandler, "EVENT_CACHE_SUFFIX" ) + ? arguments.oEventHandler.EVENT_CACHE_SUFFIX + : ""; // if the cacheFilter has a length and is a method, then we need to verify and store the resulting closure if ( len( mdEntry.cacheFilter ) ) { @@ -989,8 +991,12 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { return arguments.ehBean; } - var handler = isNull( arguments.oEventHandler ) ? newHandler( arguments.ehBean ) : arguments.oEventHandler; - var md = getMetadata( handler ); + var handler = ( + structKeyExists( arguments, "oEventHandler" ) && !isNull( arguments.oEventHandler ) + ? arguments.oEventHandler + : newHandler( arguments.ehBean ) + ); + var md = getMetadata( handler ); arguments.ehBean .setActionMetadata( handler._actionMetadata( arguments.ehBean.getMethod() ) ) diff --git a/tests/full-null/Application.cfc b/tests/full-null/Application.cfc new file mode 100644 index 000000000..9a1ac120c --- /dev/null +++ b/tests/full-null/Application.cfc @@ -0,0 +1,11 @@ +component { + + frameworkRoot = createObject( "java", "java.io.File" ) + .init( getDirectoryFromPath( getCurrentTemplatePath() ) & "../../" ) + .getCanonicalPath(); + + this.name = "coldbox-full-null-regression-#hash( frameworkRoot )#"; + this.enableNullSupport = true; + this.mappings[ "/coldbox" ] = frameworkRoot; + +} diff --git a/tests/full-null/index.cfm b/tests/full-null/index.cfm new file mode 100644 index 000000000..ff5607dbb --- /dev/null +++ b/tests/full-null/index.cfm @@ -0,0 +1,22 @@ + +env = new coldbox.system.core.delegates.Env(); +javaSystem = env.getJavaSystem(); +utility = new coldbox.system.core.util.Util(); +mixer = utility.getMixerUtil(); + +logBoxConfig = new coldbox.system.logging.config.LogBoxConfig().init().loadDataDSL( { "appenders" : {} } ); +cacheBoxConfig = new coldbox.system.cache.config.CacheBoxConfig() + .init() + .loadDataDSL( { "defaultCache" : { "coldboxEnabled" : false } } ); + +if ( + !isInstanceOf( javaSystem, "java.lang.System" ) || + !isInstanceOf( mixer, "coldbox.system.core.dynamic.MixerUtil" ) || + logBoxConfig.getRoot().appenders != "*" || + structIsEmpty( cacheBoxConfig.getMemento().defaultCache ) +) { + throw( type = "RegressionFailure", message = "ColdBox public APIs did not initialize with full null support." ); +} + +writeOutput( "PASS" ); + diff --git a/tests/specs/cache/config/CacheBoxConfigWithDataCFCTest.cfc b/tests/specs/cache/config/CacheBoxConfigWithDataCFCTest.cfc index 2277c7ca0..bc5b3fc46 100755 --- a/tests/specs/cache/config/CacheBoxConfigWithDataCFCTest.cfc +++ b/tests/specs/cache/config/CacheBoxConfigWithDataCFCTest.cfc @@ -5,6 +5,17 @@ dataConfigPath = "coldbox.tests.resources.CacheBoxConfigData"; } + function testLoadsMinimalDataDSLThroughPublicAPI(){ + var config = new coldbox.system.cache.config.CacheBoxConfig() + .init() + .loadDataDSL( { "defaultCache" : { "coldboxEnabled" : false } } ); + var memento = config.getMemento(); + + assertFalse( structIsEmpty( memento.defaultCache ) ); + assertTrue( structIsEmpty( memento.caches ) ); + assertTrue( arrayIsEmpty( memento.listeners ) ); + } + function testLoader(){ // My Data Object dataConfig = createObject( "component", dataConfigPath ); diff --git a/tests/specs/cache/providers/CFProviderTest.cfc b/tests/specs/cache/providers/CFProviderTest.cfc index de98a1680..14ebae3f8 100755 --- a/tests/specs/cache/providers/CFProviderTest.cfc +++ b/tests/specs/cache/providers/CFProviderTest.cfc @@ -97,8 +97,7 @@ results = cache.get( "test" ); assertEquals( results, testval ); - results = cache.get( "test2" ); - assertFalse( isDefined( "results" ) ); + assertTrue( isNull( cache.get( "test2" ) ) ); } function testGetOrSet(){ diff --git a/tests/specs/cache/providers/LuceeProviderTest.cfc b/tests/specs/cache/providers/LuceeProviderTest.cfc index 7290e28ef..396639232 100755 --- a/tests/specs/cache/providers/LuceeProviderTest.cfc +++ b/tests/specs/cache/providers/LuceeProviderTest.cfc @@ -92,7 +92,7 @@ // assertEquals( 1, cache.getStats().getHits() ); results = cache.get( "test2" ); - assertFalse( isDefined( "results" ) ); + assertTrue( isNull( results ) ); // assertEquals( 1, cache.getStats().getMisses() ); } diff --git a/tests/specs/core/delegates/EnvSpec.cfc b/tests/specs/core/delegates/EnvSpec.cfc index 335ddd82d..8bd5580d0 100644 --- a/tests/specs/core/delegates/EnvSpec.cfc +++ b/tests/specs/core/delegates/EnvSpec.cfc @@ -17,6 +17,13 @@ component extends="testbox.system.BaseSpec" { function run( testResults, testBox ){ // all your suites go here. describe( "Env spec", function(){ + it( "lazy loads Java System through the public API", function(){ + var freshEnv = new coldbox.system.core.delegates.Env(); + + expect( freshEnv.getJavaSystem() ).toBeInstanceOf( "java.lang.System" ); + expect( freshEnv.getJavaSystem() ).toBeSameInstanceAs( freshEnv.getJavaSystem() ); + } ); + it( "can get a system property", function(){ var systemMock = createObject( "java", "java.lang.System" ); systemMock.setProperty( "foo", "bar" ); diff --git a/tests/specs/core/util/UtilTest.cfc b/tests/specs/core/util/UtilTest.cfc index fcd8a742d..39f0c2886 100755 --- a/tests/specs/core/util/UtilTest.cfc +++ b/tests/specs/core/util/UtilTest.cfc @@ -5,6 +5,13 @@ class1 = createObject( "component", "tests.resources.Class1" ); } + function testLazyLoadsMixerUtilThroughPublicAPI(){ + var freshUtil = new coldbox.system.core.util.Util(); + + assertTrue( isInstanceOf( freshUtil.getMixerUtil(), "coldbox.system.core.dynamic.MixerUtil" ) ); + assertSame( freshUtil.getMixerUtil(), freshUtil.getMixerUtil() ); + } + function isInstanceCheck(){ test = createObject( "component", "coldbox.tests.testHandlers.BaseTest" ); assertTrue( util.isInstanceCheck( test, "coldbox.system.EventHandler" ) ); diff --git a/tests/specs/logging/config/LogBoxConfigTest.cfc b/tests/specs/logging/config/LogBoxConfigTest.cfc index aa9edea29..42de83a23 100755 --- a/tests/specs/logging/config/LogBoxConfigTest.cfc +++ b/tests/specs/logging/config/LogBoxConfigTest.cfc @@ -4,6 +4,15 @@ config = createMock( className = "coldbox.system.logging.config.LogBoxConfig" ).init(); } + function testLoadsMinimalDataDSLThroughPublicAPI(){ + var freshConfig = new coldbox.system.logging.config.LogBoxConfig() + .init() + .loadDataDSL( { "appenders" : {} } ); + + assertEquals( "*", freshConfig.getRoot().appenders ); + assertTrue( structIsEmpty( freshConfig.getAllCategories() ) ); + } + function testAddAppender(){ config.appender( "luis", "coldbox.system.logging.AbstractAppender" ); config.appender( "luis2", "coldbox.system.logging.AbstractAppender" );