@@ -1084,3 +1084,233 @@ class ArrayLiteralNode extends LocalSourceNode, ExprNode {
10841084 */
10851085 Node getAnElement ( ) { result = this .( CallNode ) .getPositionalArgument ( _) }
10861086}
1087+
1088+ /**
1089+ * A place in which a named constant can be found up during constant lookup.
1090+ */
1091+ private newtype TConstLookupScope =
1092+ /** Look up in a qualified constant name `base::`. */
1093+ MkQualifiedLookup ( ConstantAccess base ) or
1094+ /** Look up in the ancestors of `mod`. */
1095+ MkAncestorLookup ( Module mod ) or
1096+ /** Look up in a module syntactically nested in `scope`. */
1097+ MkNestedLookup ( ModuleBase scope ) or
1098+ /** Pseudo-scope for accesses that are known to resolve to `mod`. */
1099+ MkExactLookup ( Module mod )
1100+
1101+ /**
1102+ * Gets a `LocalSourceNode` to represent the constant read or written by `access`.
1103+ */
1104+ private LocalSourceNode getConstantAccessNode ( ConstantAccess access ) {
1105+ // Namespaces don't evaluate to the constant being accessed, they return the value of their last statement.
1106+ // Use the definition of 'self' in the namespace as the representative in this case.
1107+ if access instanceof Namespace
1108+ then
1109+ result .( SsaDefinitionNode ) .getDefinition ( ) .( Ssa:: SelfDefinition ) .getSourceVariable ( ) =
1110+ access .( Namespace ) .getModuleSelfVariable ( )
1111+ else result .asExpr ( ) .getExpr ( ) = access
1112+ }
1113+
1114+ /**
1115+ * An access to a constant, such as `C`, `C::D`, or a class or module declaration.
1116+ *
1117+ * See `DataFlow::getConst` for usage example.
1118+ */
1119+ class ConstRef extends LocalSourceNode {
1120+ private ConstantAccess access ;
1121+
1122+ ConstRef ( ) { this = getConstantAccessNode ( access ) }
1123+
1124+ /** Gets the underlying constant access AST node. */
1125+ ConstantAccess asConstantAccess ( ) { result = access }
1126+
1127+ /** Gets the underlying module declaration, if any. */
1128+ Namespace asNamespaceDeclaration ( ) { result = access }
1129+
1130+ /** Gets the module defined or re-opened by this constant access, if any. */
1131+ ModuleNode asModule ( ) { result .getADeclaration ( ) = access }
1132+
1133+ /**
1134+ * Gets the simple name of the constant being referenced, such as
1135+ * the `B` in `A::B`.
1136+ */
1137+ string getName ( ) { result = access .getName ( ) }
1138+
1139+ /**
1140+ * Holds if this might refer to a top-level constant.
1141+ */
1142+ predicate isPossiblyGlobal ( ) {
1143+ exists ( Module mod |
1144+ not exists ( mod .getCanonicalEnclosingModule ( ) ) and
1145+ mod .getAnImmediateReference ( ) = access
1146+ )
1147+ or
1148+ not exists ( Module mod | mod .getAnImmediateReference ( ) = access ) and
1149+ not exists ( access .getScopeExpr ( ) )
1150+ }
1151+
1152+ /**
1153+ * Gets a module for which this constant is the reference to an ancestor module.
1154+ *
1155+ * For example, `M` is the ancestry target of `C` in the following examples:
1156+ * ```rb
1157+ * class M < C {}
1158+ *
1159+ * module M
1160+ * include C
1161+ * end
1162+ *
1163+ * module M
1164+ * prepend C
1165+ * end
1166+ * ```
1167+ */
1168+ private ModuleNode getAncestryTarget ( ) { result .getAnAncestorExpr ( ) = this }
1169+
1170+ /**
1171+ * Gets a module scope in which the value of this constant is part of `Module.nesting`.
1172+ */
1173+ private ModuleBase getANestingScope ( ) {
1174+ result = this .getAncestryTarget ( ) .getADeclaration ( )
1175+ or
1176+ result .getEnclosingModule ( ) = this .getANestingScope ( )
1177+ }
1178+
1179+ /**
1180+ * Gets the known target module.
1181+ *
1182+ * We resolve these differently to prune out infeasible constant lookups.
1183+ */
1184+ private Module getExactTarget ( ) { result .getAnImmediateReference ( ) = access }
1185+
1186+ /**
1187+ * Gets a scope in which a constant lookup may access the contents of the module referenced by this constant.
1188+ */
1189+ pragma [ nomagic]
1190+ private TConstLookupScope getATargetScope ( ) {
1191+ result = MkAncestorLookup ( this .getAncestryTarget ( ) .getADirectDescendent * ( ) )
1192+ or
1193+ access = any ( ConstantAccess ac ) .getScopeExpr ( ) and
1194+ result = MkQualifiedLookup ( access )
1195+ or
1196+ result = MkNestedLookup ( this .getANestingScope ( ) )
1197+ or
1198+ result = MkExactLookup ( access .( Namespace ) .getModule ( ) )
1199+ }
1200+
1201+ /**
1202+ * Gets the scope expression, or the immediately enclosing `Namespace` (skipping over singleton classes).
1203+ *
1204+ * Top-levels are not included, since this is only needed for nested constant lookup, and unqualified constants
1205+ * at the top-level are handled by `DataFlow::getConst`, never `ConstRef.getConst`.
1206+ */
1207+ private TConstLookupScope getLookupScope ( ) {
1208+ result = MkQualifiedLookup ( access .getScopeExpr ( ) )
1209+ or
1210+ not exists ( this .getExactTarget ( ) ) and
1211+ not exists ( access .getScopeExpr ( ) ) and
1212+ not access .hasGlobalScope ( ) and
1213+ (
1214+ result = MkAncestorLookup ( access .getEnclosingModule ( ) .getNamespaceOrToplevel ( ) .getModule ( ) )
1215+ or
1216+ result = MkNestedLookup ( access .getEnclosingModule ( ) )
1217+ )
1218+ }
1219+
1220+ /**
1221+ * Holds if this can reference a constant named `name` from `scope` using a lookup of `kind`.
1222+ */
1223+ pragma [ nomagic]
1224+ private predicate accesses ( TConstLookupScope scope , string name ) {
1225+ scope = this .getLookupScope ( ) and
1226+ name = this .getName ( )
1227+ or
1228+ exists ( Module mod |
1229+ this .getExactTarget ( ) = mod .getCanonicalNestedModule ( name ) and
1230+ scope = MkExactLookup ( mod )
1231+ )
1232+ }
1233+
1234+ /**
1235+ * Gets a constant reference that may resolve to a member of this node.
1236+ *
1237+ * For example `DataFlow::getConst("A").getConst("B")` finds the following:
1238+ * ```rb
1239+ * A::B # simple reference
1240+ *
1241+ * module A
1242+ * B # in scope
1243+ * module X
1244+ * B # in nested scope
1245+ * end
1246+ * end
1247+ *
1248+ * module X
1249+ * include A
1250+ * B # via inclusion
1251+ * end
1252+ *
1253+ * class X < A
1254+ * B # via subclassing
1255+ * end
1256+ * ```
1257+ */
1258+ pragma [ inline]
1259+ ConstRef getConst ( string name ) {
1260+ exists ( TConstLookupScope scope |
1261+ pragma [ only_bind_into ] ( scope ) = pragma [ only_bind_out ] ( this ) .getATargetScope ( ) and
1262+ result .accesses ( pragma [ only_bind_out ] ( scope ) , name )
1263+ )
1264+ }
1265+
1266+ /**
1267+ * Gets a module that transitively subclasses, includes, or prepends the module referred to by
1268+ * this constant.
1269+ *
1270+ * For example, `DataFlow::getConst("A").getADescendentModule()` finds `B`, `C`, and `E`:
1271+ * ```rb
1272+ * class B < A
1273+ * end
1274+ *
1275+ * class C < B
1276+ * end
1277+ *
1278+ * module E
1279+ * include C
1280+ * end
1281+ * ```
1282+ */
1283+ ModuleNode getADescendentModule ( ) { MkAncestorLookup ( result ) = this .getATargetScope ( ) }
1284+ }
1285+
1286+ /**
1287+ * Gets a constant reference that may resolve to the top-level constant `name`.
1288+ *
1289+ * To get nested constants, call `getConst()` one or more times on the result.
1290+ *
1291+ * For example `DataFlow::getConst("A").getConst("B")` finds the following:
1292+ * ```rb
1293+ * A::B # simple reference
1294+ *
1295+ * module A
1296+ * B # in scope
1297+ * module X
1298+ * B # in nested scope
1299+ * end
1300+ * end
1301+ *
1302+ * module X
1303+ * include A
1304+ * B # via inclusion
1305+ * end
1306+ *
1307+ * class X < A
1308+ * B # via subclassing
1309+ * end
1310+ * ```
1311+ */
1312+ pragma [ nomagic]
1313+ ConstRef getConst ( string name ) {
1314+ result .getName ( ) = name and
1315+ result .isPossiblyGlobal ( )
1316+ }
0 commit comments