Skip to content

Commit 101efda

Browse files
jeet1995Copilot
andcommitted
Unify all accessor fields to static getter pattern, add enforcement test
- Convert all remaining instance accessor fields to static getter methods across 15 consuming classes (CosmosDiagnosticsContext was the last dangerous static final field running during <clinit>) - Add noStaticOrInstanceAccessorFieldsInConsumingClasses enforcement test that scans source files to prevent reintroduction of dangerous patterns - Consistent pattern: private static XxxAccessor xxx() { return getXxxAccessor(); } Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f793731 commit 101efda

16 files changed

+228
-126
lines changed

sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/ImplementationBridgeHelpersTest.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,4 +446,73 @@ public static void main(String[] args) throws Exception {
446446
}
447447
}
448448
}
449+
450+
/**
451+
* Enforces that no consuming class stores an accessor in a {@code static final} or
452+
* {@code private final} field. Such fields are initialized during {@code <clinit>} and
453+
* can trigger {@code initializeAllAccessors()}, creating circular class-initialization
454+
* lock chains that deadlock under concurrent class loading (JLS §12.4.2).
455+
* <p>
456+
* The approved pattern is a {@code private static} getter method:
457+
* <pre>{@code
458+
* private static XxxAccessor xxxAccessor() {
459+
* return ImplementationBridgeHelpers.XxxHelper.getXxxAccessor();
460+
* }
461+
* }</pre>
462+
*/
463+
@Test(groups = { "unit" })
464+
public void noStaticOrInstanceAccessorFieldsInConsumingClasses() throws Exception {
465+
// Scan all Java source files for dangerous accessor field patterns
466+
java.nio.file.Path cosmosRoot = java.nio.file.Paths.get(
467+
System.getProperty("user.dir"))
468+
.getParent() // azure-cosmos-tests -> sdk/cosmos
469+
.resolve("azure-cosmos")
470+
.resolve("src")
471+
.resolve("main")
472+
.resolve("java");
473+
474+
assertThat(java.nio.file.Files.exists(cosmosRoot))
475+
.as("azure-cosmos source root must exist at: " + cosmosRoot)
476+
.isTrue();
477+
478+
// Files that legitimately declare accessor AtomicReference fields
479+
java.util.Set<String> exemptFiles = new java.util.HashSet<>(java.util.Arrays.asList(
480+
"ImplementationBridgeHelpers.java",
481+
"BridgeInternal.java",
482+
"ModelBridgeInternal.java",
483+
"UtilBridgeInternal.java"
484+
));
485+
486+
// Patterns that indicate a dangerous field declaration:
487+
// - "private final XxxAccessor fieldName ="
488+
// - "private static final XxxAccessor fieldName ="
489+
// - "private final static XxxAccessor fieldName ="
490+
// Method declarations (containing "()" after the name) are safe.
491+
java.util.regex.Pattern dangerousPattern = java.util.regex.Pattern.compile(
492+
"private\\s+(final\\s+static|static\\s+final|final)\\s+\\S*Accessor\\s+\\w+\\s*=");
493+
494+
List<String> violations = new ArrayList<>();
495+
496+
java.nio.file.Files.walk(cosmosRoot)
497+
.filter(p -> p.toString().endsWith(".java"))
498+
.filter(p -> !exemptFiles.contains(p.getFileName().toString()))
499+
.forEach(p -> {
500+
try {
501+
List<String> lines = java.nio.file.Files.readAllLines(p);
502+
for (int i = 0; i < lines.size(); i++) {
503+
String line = lines.get(i);
504+
if (dangerousPattern.matcher(line).find()) {
505+
violations.add(cosmosRoot.relativize(p) + ":" + (i + 1) + " → " + line.trim());
506+
}
507+
}
508+
} catch (Exception e) {
509+
// skip unreadable files
510+
}
511+
});
512+
513+
assertThat(violations)
514+
.as("Found accessor fields that run during <clinit> — use 'private static XxxAccessor xxx() "
515+
+ "{ return getXxxAccessor(); }' instead:\n" + String.join("\n", violations))
516+
.isEmpty();
517+
}
449518
}

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/CosmosAsyncClient.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,13 @@
7979
builder = CosmosClientBuilder.class,
8080
isAsync = true)
8181
public final class CosmosAsyncClient implements Closeable {
82-
private final ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.CosmosQueryRequestOptionsAccessor
83-
queryOptionsAccessor = ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.getCosmosQueryRequestOptionsAccessor();
84-
private final ImplementationBridgeHelpers.FeedResponseHelper.FeedResponseAccessor
85-
feedResponseAccessor = ImplementationBridgeHelpers.FeedResponseHelper.getFeedResponseAccessor();
82+
private static ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.CosmosQueryRequestOptionsAccessor queryOptionsAccessor() {
83+
return ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.getCosmosQueryRequestOptionsAccessor();
84+
}
85+
86+
private static ImplementationBridgeHelpers.FeedResponseHelper.FeedResponseAccessor feedResponseAccessor() {
87+
return ImplementationBridgeHelpers.FeedResponseHelper.getFeedResponseAccessor();
88+
}
8689

8790
private static final Logger logger = LoggerFactory.getLogger(CosmosAsyncClient.class);
8891

@@ -466,7 +469,7 @@ CosmosPagedFlux<CosmosDatabaseProperties> readAllDatabases(CosmosQueryRequestOpt
466469
null,
467470
ResourceType.Database,
468471
OperationType.ReadFeed,
469-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
472+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
470473
nonNullOptions,
471474
pagedFluxOptions
472475
);
@@ -475,7 +478,7 @@ CosmosPagedFlux<CosmosDatabaseProperties> readAllDatabases(CosmosQueryRequestOpt
475478

476479
return getDocClientWrapper().readDatabases(state)
477480
.map(response ->
478-
feedResponseAccessor.createFeedResponse(
481+
feedResponseAccessor().createFeedResponse(
479482
ModelBridgeInternal.getCosmosDatabasePropertiesFromV2Results(response.getResults()),
480483
response.getResponseHeaders(),
481484
response.getCosmosDiagnostics()));
@@ -652,15 +655,15 @@ private CosmosPagedFlux<CosmosDatabaseProperties> queryDatabasesInternal(
652655
null,
653656
ResourceType.Database,
654657
OperationType.Query,
655-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
658+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
656659
nonNullOptions,
657660
pagedFluxOptions
658661
);
659662

660663
pagedFluxOptions.setFeedOperationState(state);
661664

662665
return getDocClientWrapper().queryDatabases(querySpec, state)
663-
.map(response -> feedResponseAccessor.createFeedResponse(
666+
.map(response -> feedResponseAccessor().createFeedResponse(
664667
ModelBridgeInternal.getCosmosDatabasePropertiesFromV2Results(response.getResults()),
665668
response.getResponseHeaders(),
666669
response.getCosmosDiagnostics()));

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/CosmosAsyncDatabase.java

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,13 @@
4444
* Perform read and delete databases, update database throughput, and perform operations on child resources
4545
*/
4646
public class CosmosAsyncDatabase {
47-
private final ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.CosmosQueryRequestOptionsAccessor
48-
queryOptionsAccessor = ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.getCosmosQueryRequestOptionsAccessor();
49-
private final ImplementationBridgeHelpers.FeedResponseHelper.FeedResponseAccessor
50-
feedResponseAccessor = ImplementationBridgeHelpers.FeedResponseHelper.getFeedResponseAccessor();
47+
private static ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.CosmosQueryRequestOptionsAccessor queryOptionsAccessor() {
48+
return ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.getCosmosQueryRequestOptionsAccessor();
49+
}
50+
51+
private static ImplementationBridgeHelpers.FeedResponseHelper.FeedResponseAccessor feedResponseAccessor() {
52+
return ImplementationBridgeHelpers.FeedResponseHelper.getFeedResponseAccessor();
53+
}
5154

5255

5356
private final CosmosAsyncClient client;
@@ -648,15 +651,15 @@ public CosmosPagedFlux<CosmosContainerProperties> readAllContainers(CosmosQueryR
648651
null,
649652
ResourceType.DocumentCollection,
650653
OperationType.ReadFeed,
651-
queryOptionsAccessor.getQueryNameOrDefault(requestOptions, spanName),
654+
queryOptionsAccessor().getQueryNameOrDefault(requestOptions, spanName),
652655
requestOptions,
653656
pagedFluxOptions
654657
);
655658

656659
pagedFluxOptions.setFeedOperationState(state);
657660

658661
return getDocClientWrapper().readCollections(getLink(), state)
659-
.map(response -> feedResponseAccessor.createFeedResponse(
662+
.map(response -> feedResponseAccessor().createFeedResponse(
660663
ModelBridgeInternal.getCosmosContainerPropertiesFromV2Results(response.getResults()),
661664
response.getResponseHeaders(),
662665
response.getCosmosDiagnostics()));
@@ -956,15 +959,15 @@ CosmosPagedFlux<CosmosUserProperties> readAllUsers(CosmosQueryRequestOptions opt
956959
null,
957960
ResourceType.User,
958961
OperationType.ReadFeed,
959-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
962+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
960963
nonNullOptions,
961964
pagedFluxOptions
962965
);
963966

964967
pagedFluxOptions.setFeedOperationState(state);
965968

966969
return getDocClientWrapper().readUsers(getLink(), state)
967-
.map(response -> feedResponseAccessor.createFeedResponse(
970+
.map(response -> feedResponseAccessor().createFeedResponse(
968971
ModelBridgeInternal.getCosmosUserPropertiesFromV2Results(response.getResults()), response
969972
.getResponseHeaders(),
970973
response.getCosmosDiagnostics()));
@@ -1019,15 +1022,15 @@ public CosmosPagedFlux<CosmosClientEncryptionKeyProperties> readAllClientEncrypt
10191022
null,
10201023
ResourceType.ClientEncryptionKey,
10211024
OperationType.ReadFeed,
1022-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
1025+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
10231026
nonNullOptions,
10241027
pagedFluxOptions
10251028
);
10261029

10271030
pagedFluxOptions.setFeedOperationState(state);
10281031

10291032
return getDocClientWrapper().readClientEncryptionKeys(getLink(), state)
1030-
.map(response -> feedResponseAccessor.createFeedResponse(
1033+
.map(response -> feedResponseAccessor().createFeedResponse(
10311034
ModelBridgeInternal.getClientEncryptionKeyPropertiesList(response.getResults()), response
10321035
.getResponseHeaders(),
10331036
response.getCosmosDiagnostics()));
@@ -1120,7 +1123,7 @@ private CosmosPagedFlux<CosmosClientEncryptionKeyProperties> queryClientEncrypti
11201123
null,
11211124
ResourceType.ClientEncryptionKey,
11221125
OperationType.Query,
1123-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
1126+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
11241127
nonNullOptions,
11251128
pagedFluxOptions
11261129
);
@@ -1304,15 +1307,15 @@ private CosmosPagedFlux<CosmosContainerProperties> queryContainersInternal(SqlQu
13041307
null,
13051308
ResourceType.DocumentCollection,
13061309
OperationType.Query,
1307-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
1310+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
13081311
nonNullOptions,
13091312
pagedFluxOptions
13101313
);
13111314

13121315
pagedFluxOptions.setFeedOperationState(state);
13131316

13141317
return getDocClientWrapper().queryCollections(getLink(), querySpec, state)
1315-
.map(response -> feedResponseAccessor.createFeedResponse(
1318+
.map(response -> feedResponseAccessor().createFeedResponse(
13161319
ModelBridgeInternal.getCosmosContainerPropertiesFromV2Results(response.getResults()),
13171320
response.getResponseHeaders(),
13181321
response.getCosmosDiagnostics()));
@@ -1332,7 +1335,7 @@ private CosmosPagedFlux<CosmosUserProperties> queryUsersInternal(SqlQuerySpec qu
13321335
null,
13331336
ResourceType.User,
13341337
OperationType.Query,
1335-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
1338+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
13361339
nonNullOptions,
13371340
pagedFluxOptions
13381341
);

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/CosmosAsyncScripts.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,13 @@
3232
* and Triggers
3333
*/
3434
public class CosmosAsyncScripts {
35-
private final ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.CosmosQueryRequestOptionsAccessor
36-
queryOptionsAccessor = ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.getCosmosQueryRequestOptionsAccessor();
37-
private final ImplementationBridgeHelpers.FeedResponseHelper.FeedResponseAccessor
38-
feedResponseAccessor = ImplementationBridgeHelpers.FeedResponseHelper.getFeedResponseAccessor();
35+
private static ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.CosmosQueryRequestOptionsAccessor queryOptionsAccessor() {
36+
return ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.getCosmosQueryRequestOptionsAccessor();
37+
}
38+
39+
private static ImplementationBridgeHelpers.FeedResponseHelper.FeedResponseAccessor feedResponseAccessor() {
40+
return ImplementationBridgeHelpers.FeedResponseHelper.getFeedResponseAccessor();
41+
}
3942

4043

4144
private final CosmosAsyncContainer container;
@@ -129,7 +132,7 @@ CosmosPagedFlux<CosmosStoredProcedureProperties> readAllStoredProcedures(CosmosQ
129132
this.container.getId(),
130133
ResourceType.StoredProcedure,
131134
OperationType.ReadFeed,
132-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
135+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
133136
nonNullOptions,
134137
pagedFluxOptions
135138
);
@@ -138,7 +141,7 @@ CosmosPagedFlux<CosmosStoredProcedureProperties> readAllStoredProcedures(CosmosQ
138141

139142
return database.getDocClientWrapper()
140143
.readStoredProcedures(container.getLink(), state)
141-
.map(response -> feedResponseAccessor.createFeedResponse(
144+
.map(response -> feedResponseAccessor().createFeedResponse(
142145
ModelBridgeInternal.getCosmosStoredProcedurePropertiesFromV2Results(response.getResults()),
143146
response.getResponseHeaders(),
144147
response.getCosmosDiagnostics()));
@@ -262,7 +265,7 @@ CosmosPagedFlux<CosmosUserDefinedFunctionProperties> readAllUserDefinedFunctions
262265
this.container.getId(),
263266
ResourceType.UserDefinedFunction,
264267
OperationType.ReadFeed,
265-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
268+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
266269
nonNullOptions,
267270
pagedFluxOptions
268271
);
@@ -271,7 +274,7 @@ CosmosPagedFlux<CosmosUserDefinedFunctionProperties> readAllUserDefinedFunctions
271274

272275
return database.getDocClientWrapper()
273276
.readUserDefinedFunctions(container.getLink(), state)
274-
.map(response -> feedResponseAccessor.createFeedResponse(
277+
.map(response -> feedResponseAccessor().createFeedResponse(
275278
ModelBridgeInternal.getCosmosUserDefinedFunctionPropertiesFromV2Results(response.getResults()),
276279
response.getResponseHeaders(),
277280
response.getCosmosDiagnostics()));
@@ -394,7 +397,7 @@ CosmosPagedFlux<CosmosTriggerProperties> readAllTriggers(CosmosQueryRequestOptio
394397
this.container.getId(),
395398
ResourceType.Trigger,
396399
OperationType.ReadFeed,
397-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
400+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
398401
nonNullOptions,
399402
pagedFluxOptions
400403
);
@@ -403,7 +406,7 @@ CosmosPagedFlux<CosmosTriggerProperties> readAllTriggers(CosmosQueryRequestOptio
403406

404407
return database.getDocClientWrapper()
405408
.readTriggers(container.getLink(), state)
406-
.map(response -> feedResponseAccessor.createFeedResponse(
409+
.map(response -> feedResponseAccessor().createFeedResponse(
407410
ModelBridgeInternal.getCosmosTriggerPropertiesFromV2Results(response.getResults()),
408411
response.getResponseHeaders(),
409412
response.getCosmosDiagnostics()));
@@ -477,7 +480,7 @@ private CosmosPagedFlux<CosmosStoredProcedureProperties> queryStoredProceduresIn
477480
this.container.getId(),
478481
ResourceType.StoredProcedure,
479482
OperationType.Query,
480-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
483+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
481484
nonNullOptions,
482485
pagedFluxOptions
483486
);
@@ -486,7 +489,7 @@ private CosmosPagedFlux<CosmosStoredProcedureProperties> queryStoredProceduresIn
486489

487490
return database.getDocClientWrapper()
488491
.queryStoredProcedures(container.getLink(), querySpec, state)
489-
.map(response -> feedResponseAccessor.createFeedResponse(
492+
.map(response -> feedResponseAccessor().createFeedResponse(
490493
ModelBridgeInternal.getCosmosStoredProcedurePropertiesFromV2Results(response.getResults()),
491494
response.getResponseHeaders(),
492495
response.getCosmosDiagnostics()));
@@ -508,7 +511,7 @@ private CosmosPagedFlux<CosmosUserDefinedFunctionProperties> queryUserDefinedFun
508511
this.container.getId(),
509512
ResourceType.UserDefinedFunction,
510513
OperationType.Query,
511-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
514+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
512515
nonNullOptions,
513516
pagedFluxOptions
514517
);
@@ -517,7 +520,7 @@ private CosmosPagedFlux<CosmosUserDefinedFunctionProperties> queryUserDefinedFun
517520

518521
return database.getDocClientWrapper()
519522
.queryUserDefinedFunctions(container.getLink(), querySpec, state)
520-
.map(response -> feedResponseAccessor.createFeedResponse(
523+
.map(response -> feedResponseAccessor().createFeedResponse(
521524
ModelBridgeInternal.getCosmosUserDefinedFunctionPropertiesFromV2Results(response.getResults()),
522525
response.getResponseHeaders(),
523526
response.getCosmosDiagnostics()));
@@ -546,7 +549,7 @@ private CosmosPagedFlux<CosmosTriggerProperties> queryTriggersInternal(
546549
this.container.getId(),
547550
ResourceType.Trigger,
548551
OperationType.Query,
549-
queryOptionsAccessor.getQueryNameOrDefault(nonNullOptions, spanName),
552+
queryOptionsAccessor().getQueryNameOrDefault(nonNullOptions, spanName),
550553
nonNullOptions,
551554
pagedFluxOptions
552555
);
@@ -555,7 +558,7 @@ private CosmosPagedFlux<CosmosTriggerProperties> queryTriggersInternal(
555558

556559
return database.getDocClientWrapper()
557560
.queryTriggers(container.getLink(), querySpec, state)
558-
.map(response -> feedResponseAccessor.createFeedResponse(
561+
.map(response -> feedResponseAccessor().createFeedResponse(
559562
ModelBridgeInternal.getCosmosTriggerPropertiesFromV2Results(response.getResults()),
560563
response.getResponseHeaders(),
561564
response.getCosmosDiagnostics()));

0 commit comments

Comments
 (0)