Skip to content

Commit b632e21

Browse files
committed
Ruby: add ConstRef
1 parent 06ec03d commit b632e21

1 file changed

Lines changed: 230 additions & 0 deletions

File tree

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)