61
61
import java .util .List ;
62
62
import java .util .Locale ;
63
63
import java .util .Set ;
64
+ import java .util .function .Supplier ;
64
65
import java .util .regex .Pattern ;
65
66
import java .util .regex .PatternSyntaxException ;
66
67
import org .jspecify .nullness .Nullable ;
@@ -1052,7 +1053,26 @@ private static String removeTypeDecl(String specName) throws InvalidRequirementS
1052
1053
return specName .substring (index + 1 );
1053
1054
}
1054
1055
1055
- private static @ Nullable String inferStringValue (@ Nullable Scope scope , Node node ) {
1056
+ private static boolean isAnnotatedAsConst (JSDocInfo jsdoc ) {
1057
+ return jsdoc != null && jsdoc .isConstant ();
1058
+ }
1059
+
1060
+ private static @ Nullable String getValueFromGlobalName (
1061
+ @ Nullable Scope scope , GlobalNamespace gns , String qualifiedName ) {
1062
+ GlobalNamespace .Name gn = gns .getOwnSlot (qualifiedName );
1063
+ if (gn == null
1064
+ || gn .getGlobalSets () != 1
1065
+ || gn .getTotalSets () != 1
1066
+ || !isAnnotatedAsConst (gn .getJSDocInfo ())) {
1067
+ return null ;
1068
+ }
1069
+
1070
+ return inferStringValue (
1071
+ scope , NodeUtil .getRValueOfLValue (gn .getDeclaration ().getNode ()), () -> gns );
1072
+ }
1073
+
1074
+ private static @ Nullable String inferStringValue (
1075
+ @ Nullable Scope scope , Node node , Supplier <GlobalNamespace > globalNamespaceSupplier ) {
1056
1076
if (node == null ) {
1057
1077
return null ;
1058
1078
}
@@ -1072,35 +1092,43 @@ private static String removeTypeDecl(String specName) throws InvalidRequirementS
1072
1092
if (var == null ) {
1073
1093
return null ;
1074
1094
}
1075
- if (!var .isConst ()) {
1076
- JSDocInfo jsdocInfo = var .getJSDocInfo ();
1077
- if (jsdocInfo == null || !jsdocInfo .isConstant ()) {
1078
- return null ;
1079
- }
1095
+ if (!var .isConst () && !isAnnotatedAsConst (var .getJSDocInfo ())) {
1096
+ return null ;
1080
1097
}
1081
1098
Node initialValue = var .getInitialValue ();
1082
- return inferStringValue (var .getScope (), initialValue );
1099
+ return inferStringValue (var .getScope (), initialValue , globalNamespaceSupplier );
1083
1100
case GETPROP :
1084
1101
JSType type = node .getJSType ();
1085
- if (type == null || !type .isEnumElementType ()) {
1086
- // For simplicity, only support enums. The JS style guide requires enums to be
1087
- // effectively immutable and all enum items should be statically known.
1088
- // See go/js-style#features-objects-enums.
1102
+ if (type == null ) {
1089
1103
return null ;
1090
1104
}
1091
1105
1092
- Node enumSource = type .toMaybeEnumElementType ().getEnumType ().getSource ();
1093
- if (enumSource == null ) {
1094
- return null ;
1095
- }
1096
- if (!enumSource .isObjectLit () && !enumSource .isClassMembers ()) {
1097
- return null ;
1106
+ // The JS style guide requires enums to be effectively immutable and all enum items should
1107
+ // be statically known. See go/js-style#features-objects-enums.
1108
+ if (type .isEnumElementType ()) {
1109
+ Node enumSource = type .toMaybeEnumElementType ().getEnumType ().getSource ();
1110
+ if (enumSource == null ) {
1111
+ return null ;
1112
+ }
1113
+ if (!enumSource .isObjectLit () && !enumSource .isClassMembers ()) {
1114
+ return null ;
1115
+ }
1116
+ return inferStringValue (
1117
+ null ,
1118
+ NodeUtil .getFirstPropMatchingKey (enumSource , node .getString ()),
1119
+ globalNamespaceSupplier );
1120
+ } else if (type .isString ()) {
1121
+ GlobalNamespace gns = globalNamespaceSupplier .get ();
1122
+ if (gns == null ) {
1123
+ return null ;
1124
+ }
1125
+ return getValueFromGlobalName (scope , gns , node .getQualifiedName ());
1098
1126
}
1099
- return inferStringValue (
1100
- null , NodeUtil .getFirstPropMatchingKey (enumSource , node .getString ()));
1101
- default :
1102
1127
return null ;
1128
+ default :
1129
+ // Do nothing
1103
1130
}
1131
+ return null ;
1104
1132
}
1105
1133
1106
1134
private static boolean isXid (JSType type ) {
@@ -2316,14 +2344,25 @@ public static final class SecuritySensitiveAttributes {
2316
2344
"data" );
2317
2345
2318
2346
private final ImmutableSet <String > bannedAtrrs ;
2347
+ private final Supplier <GlobalNamespace > globalNamespaceSupplier ;
2319
2348
2320
2349
public SecuritySensitiveAttributes () {
2321
- this .bannedAtrrs = ALL_BANNED_ATTRS ;
2350
+ this (ALL_BANNED_ATTRS , () -> null );
2351
+ }
2352
+
2353
+ public SecuritySensitiveAttributes (Supplier <GlobalNamespace > globalNamespaceSupplier ) {
2354
+ this (ALL_BANNED_ATTRS , globalNamespaceSupplier );
2322
2355
}
2323
2356
2324
2357
public SecuritySensitiveAttributes (Collection <String > bannedAtrrs ) {
2358
+ this (bannedAtrrs , () -> null );
2359
+ }
2360
+
2361
+ public SecuritySensitiveAttributes (
2362
+ Collection <String > bannedAtrrs , Supplier <GlobalNamespace > globalNamespaceSupplier ) {
2325
2363
this .bannedAtrrs =
2326
2364
bannedAtrrs .stream ().map (s -> s .toLowerCase (Locale .ROOT )).collect (toImmutableSet ());
2365
+ this .globalNamespaceSupplier = globalNamespaceSupplier ;
2327
2366
}
2328
2367
2329
2368
/**
@@ -2343,7 +2382,8 @@ public boolean contains(String attributeName) {
2343
2382
*/
2344
2383
public ConformanceResult checkConformanceForAttributeName (
2345
2384
NodeTraversal traversal , Node attrName ) {
2346
- String literalName = ConformanceUtil .inferStringValue (traversal .getScope (), attrName );
2385
+ String literalName =
2386
+ ConformanceUtil .inferStringValue (traversal .getScope (), attrName , globalNamespaceSupplier );
2347
2387
if (literalName == null ) {
2348
2388
// xid() obfuscates attribute names, thus never clashing with security-sensitive attributes.
2349
2389
return ConformanceUtil .isXid (attrName .getJSType ())
@@ -2368,7 +2408,8 @@ public ConformanceResult checkConformanceForAttributeName(
2368
2408
*/
2369
2409
public ConformanceResult checkConformanceForAttributeNameWithHighConfidence (
2370
2410
NodeTraversal traversal , Node attrName ) {
2371
- String literalName = ConformanceUtil .inferStringValue (traversal .getScope (), attrName );
2411
+ String literalName =
2412
+ ConformanceUtil .inferStringValue (traversal .getScope (), attrName , globalNamespaceSupplier );
2372
2413
if (literalName == null ) {
2373
2414
return ConformanceResult .CONFORMANCE ;
2374
2415
}
@@ -2396,6 +2437,15 @@ public static final class BanElementSetAttribute extends AbstractRule {
2396
2437
private static final ImmutableSet <String > BANNED_PROPERTIES =
2397
2438
ImmutableSet .of (SET_ATTRIBUTE , SET_ATTRIBUTE_NS , SET_ATTRIBUTE_NODE , SET_ATTRIBUTE_NODE_NS );
2398
2439
2440
+ private boolean performGlobalNamespaceAnalysis = true ;
2441
+ private GlobalNamespace globalNamespace ;
2442
+
2443
+ /**
2444
+ * The cap for script size, beyond which we give up performing global namespace analysis due to
2445
+ * exccessive performance cost. The value is purely herustic and subject to future regulation.
2446
+ */
2447
+ private static final int GLOBAL_NAMESPACE_ANALYSIS_LIMIT = 10000 ;
2448
+
2399
2449
private final JSType elementType ;
2400
2450
private final SecuritySensitiveAttributes securitySensitiveAttributes ;
2401
2451
private final ConformanceResult defaultDecisionForUncertainCases ;
@@ -2409,14 +2459,31 @@ public static final class BanElementSetAttribute extends AbstractRule {
2409
2459
public BanElementSetAttribute (AbstractCompiler compiler , Requirement requirement )
2410
2460
throws InvalidRequirementSpec {
2411
2461
super (compiler , requirement );
2412
- securitySensitiveAttributes = new SecuritySensitiveAttributes (requirement .getValueList ());
2462
+ securitySensitiveAttributes =
2463
+ new SecuritySensitiveAttributes (requirement .getValueList (), this ::getGlobalNamespace );
2413
2464
defaultDecisionForUncertainCases =
2414
2465
requirement .getReportLooseTypeViolations ()
2415
2466
? ConformanceResult .VIOLATION
2416
2467
: ConformanceResult .CONFORMANCE ;
2417
2468
elementType = compiler .getTypeRegistry ().getGlobalType (ELEMENT_TYPE_NAME );
2418
2469
}
2419
2470
2471
+ /**
2472
+ * Build GlobalNamespace starting from the Root node of the input, excluding externs. Skip the
2473
+ * build if the input AST is too large.
2474
+ */
2475
+ @ Nullable GlobalNamespace getGlobalNamespace () {
2476
+ if (performGlobalNamespaceAnalysis && globalNamespace == null ) {
2477
+ if (NodeUtil .countAstSizeUpToLimit (compiler .getJsRoot (), GLOBAL_NAMESPACE_ANALYSIS_LIMIT )
2478
+ >= GLOBAL_NAMESPACE_ANALYSIS_LIMIT ) {
2479
+ performGlobalNamespaceAnalysis = false ;
2480
+ } else {
2481
+ globalNamespace = new GlobalNamespace (compiler , compiler .getJsRoot ());
2482
+ }
2483
+ }
2484
+ return globalNamespace ;
2485
+ }
2486
+
2420
2487
@ Override
2421
2488
protected ConformanceResult checkConformance (NodeTraversal traversal , Node node ) {
2422
2489
if (node .isCall ()) {
@@ -2502,7 +2569,8 @@ private Optional<String> getBannedPropertyName(Node callNode) {
2502
2569
private ConformanceResult checkConformanceOnGetElement (
2503
2570
NodeTraversal traversal , Node getElementNode ) {
2504
2571
Node key = getElementNode .getSecondChild ();
2505
- String keyName = ConformanceUtil .inferStringValue (traversal .getScope (), key );
2572
+ String keyName =
2573
+ ConformanceUtil .inferStringValue (traversal .getScope (), key , this ::getGlobalNamespace );
2506
2574
if (keyName != null ) {
2507
2575
if (securitySensitiveAttributes .contains (keyName .toLowerCase (Locale .ROOT ))
2508
2576
|| (requirement .getReportLooseTypeViolations ()
0 commit comments