Skip to content

Commit cac2e2e

Browse files
authored
Merge pull request github#10928 from asgerf/rb/assumed-global-const
Ruby: assume some global constants are defined
2 parents 22adf21 + 0ffb0f6 commit cac2e2e

File tree

9 files changed

+175
-18
lines changed

9 files changed

+175
-18
lines changed

ruby/ql/lib/codeql/ruby/ast/Module.qll

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ class Module extends TModule {
2525

2626
/** Holds if this module is a class. */
2727
pragma[noinline]
28-
predicate isClass() { this.getADeclaration() instanceof ClassDeclaration }
28+
predicate isClass() {
29+
this.getADeclaration() instanceof ClassDeclaration
30+
or
31+
// If another class extends this, but we can't see the class declaration, assume it's a class
32+
getSuperClass(_) = this
33+
}
2934

3035
/** Gets a textual representation of this module. */
3136
string toString() {

ruby/ql/lib/codeql/ruby/ast/internal/Module.qll

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
private import codeql.ruby.AST
2+
private import Scope as Scope
23

34
// Names of built-in modules and classes
45
private string builtin() {
56
result =
67
[
78
"Object", "Kernel", "BasicObject", "Class", "Module", "NilClass", "FalseClass", "TrueClass",
8-
"Numeric", "Integer", "Float", "Rational", "Complex", "Array", "Hash", "Symbol", "Proc"
9+
"Numeric", "Integer", "Float", "Rational", "Complex", "Array", "Hash", "String", "Symbol",
10+
"Proc",
911
]
1012
}
1113

@@ -16,6 +18,8 @@ private module Cached {
1618
TResolved(string qName) {
1719
qName = builtin()
1820
or
21+
qName = getAnAssumedGlobalConst()
22+
or
1923
qName = namespaceDeclaration(_)
2024
} or
2125
TUnresolved(Namespace n) { not exists(namespaceDeclaration(n)) }
@@ -38,7 +42,10 @@ private module Cached {
3842
Module getSuperClass(Module cls) {
3943
cls = TResolved("Object") and result = TResolved("BasicObject")
4044
or
41-
cls = TResolved(["Module", "Numeric", "Array", "Hash", "FalseClass", "TrueClass", "NilClass"]) and
45+
cls =
46+
TResolved([
47+
"Module", "Numeric", "Array", "Hash", "FalseClass", "TrueClass", "NilClass", "String"
48+
]) and
4249
result = TResolved("Object")
4350
or
4451
cls = TResolved(["Integer", "Float", "Rational", "Complex"]) and
@@ -58,14 +65,20 @@ private module Cached {
5865
forex(ClassDeclaration d | d = cls.getADeclaration() |
5966
not exists(resolveConstantReadAccess(d.getSuperclassExpr()))
6067
)
68+
or
69+
// If a module is used as a base class of another class, but we don't see its class declaration
70+
// treat it as a class extending Object, so its subclasses transitively extend Object.
71+
result = TResolved("Object") and
72+
not cls.getADeclaration() instanceof ClassDeclaration and
73+
cls = resolveConstantReadAccess(any(ClassDeclaration d).getSuperclassExpr())
6174
)
6275
}
6376

6477
private Module getACludedModule(IncludeOrPrependCall c, Module m) {
6578
(
6679
m = resolveConstantReadAccess(c.getReceiver())
6780
or
68-
m = enclosingModule(c).getModule() and
81+
m = enclosingModuleNoBlock(c).getModule() and
6982
c.getReceiver() instanceof SelfVariableAccess
7083
) and
7184
result = resolveConstantReadAccess(c.getAnArgument())
@@ -388,11 +401,23 @@ private module ResolveImpl {
388401
result = resolveConstantWriteAccessRec(c, _, _)
389402
}
390403

404+
/**
405+
* Gets the name of a constant `C` that we assume to be defined in the top-level because
406+
* it is referenced in a way that can only resolve to a top-level constant.
407+
*/
408+
string getAnAssumedGlobalConst() {
409+
exists(ConstantAccess access |
410+
not exists(access.getScopeExpr()) and
411+
result = access.getName() and
412+
isToplevel(access)
413+
)
414+
}
415+
391416
pragma[nomagic]
392417
private string isDefinedConstantNonRec(string container, string name) {
393418
result = resolveConstantWriteAccessNonRec(_, container, name)
394419
or
395-
result = builtin() and
420+
result = [builtin(), getAnAssumedGlobalConst()] and
396421
name = result and
397422
container = "Object"
398423
}
@@ -447,7 +472,7 @@ private module ResolveImpl {
447472
result = resolveConstantReadAccess(this.getReceiver(), _)
448473
or
449474
exists(ModuleBase encl |
450-
encl = enclosingModule(this) and
475+
encl = enclosingModuleNoBlock(this) and
451476
result = [qualifiedModuleNameNonRec(encl, _, _), qualifiedModuleNameRec(encl, _, _)]
452477
|
453478
this.getReceiver() instanceof SelfVariableAccess
@@ -495,23 +520,31 @@ private module ResolveImpl {
495520
private import ResolveImpl
496521

497522
/**
498-
* A variant of AstNode::getEnclosingModule that excludes
523+
* Gets an enclosing scope of `scope`, stopping at the first module or block.
524+
*
525+
* Includes `scope` itself and the final module/block.
526+
*/
527+
private Scope enclosingScopesNoBlock(Scope scope) {
528+
result = scope
529+
or
530+
not scope instanceof ModuleBase and
531+
not scope instanceof Block and
532+
result = enclosingScopesNoBlock(scope.getOuterScope())
533+
}
534+
535+
/**
536+
* A variant of `AstNode::getEnclosingModule` that excludes
499537
* results that are enclosed in a block. This is a bit wrong because
500538
* it could lead to false negatives. However, `include` statements in
501539
* blocks are very rare in normal code. The majority of cases are in calls
502540
* to methods like `module_eval` and `Rspec.describe` / `Rspec.context`. These
503541
* methods evaluate the block in the context of some other module/class instead of
504542
* the enclosing one.
505543
*/
506-
private ModuleBase enclosingModule(AstNode node) {
507-
result = node.getParent()
508-
or
509-
exists(AstNode mid |
510-
result = enclosingModule(mid) and
511-
mid = node.getParent() and
512-
not mid instanceof ModuleBase and
513-
not mid instanceof Block
514-
)
544+
private ModuleBase enclosingModuleNoBlock(Stmt node) {
545+
// Note: don't rely on AstNode.getParent() here.
546+
// Instead use Scope.getOuterScope() to correctly handle the scoping of things like Namespace.getScopeExpr().
547+
result = enclosingScopesNoBlock(Scope::scopeOfInclSynth(node))
515548
}
516549

517550
private Module getAncestors(Module m) {

ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ private import internal.ControlFlowGraphImpl
77
private import internal.Splitting
88
private import internal.Completion
99

10-
/** An AST node with an associated control-flow graph. */
10+
/**
11+
* An AST node with an associated control-flow graph.
12+
*
13+
* Top-levels, methods, blocks, and lambdas are all CFG scopes.
14+
*
15+
* Note that module declarations are not themselves CFG scopes, as they are part of
16+
* the CFG of the enclosing top-level or callable.
17+
*/
1118
class CfgScope extends Scope instanceof CfgScopeImpl {
1219
/** Gets the CFG scope that this scope is nested under, if any. */
1320
final CfgScope getOuterCfgScope() {

ruby/ql/test/library-tests/modules/ancestors.expected

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
#-----| Class
22
#-----| super -> Module
33

4+
#-----| EsotericInstanceMethods
5+
6+
#-----| MyStruct
7+
8+
#-----| Struct
9+
10+
#-----| UnresolvedNamespace
11+
412
#-----| BasicObject
513

614
#-----| Complex
@@ -239,3 +247,16 @@ toplevel_self_singleton.rb:
239247
#-----| super -> Object
240248

241249
# 24| Good
250+
251+
unresolved_subclass.rb:
252+
# 1| ResolvableBaseClass
253+
#-----| super -> Object
254+
255+
# 4| UnresolvedNamespace::Subclass1
256+
#-----| super -> ResolvableBaseClass
257+
258+
# 7| UnresolvedNamespace::Subclass2
259+
#-----| super -> UnresolvedNamespace::Subclass1
260+
261+
# 11| UnresolvedNamespace::A
262+
#-----| super -> Object

ruby/ql/test/library-tests/modules/callgraph.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ getTarget
281281
| private.rb:104:1:104:20 | call to new | calls.rb:117:5:117:16 | new |
282282
| private.rb:104:1:104:28 | call to call_m1 | private.rb:91:3:93:5 | call_m1 |
283283
| private.rb:105:1:105:20 | call to new | calls.rb:117:5:117:16 | new |
284+
| toplevel_self_singleton.rb:18:12:22:1 | call to new | calls.rb:117:5:117:16 | new |
284285
| toplevel_self_singleton.rb:30:13:30:19 | call to call_me | toplevel_self_singleton.rb:26:9:27:11 | call_me |
285286
| toplevel_self_singleton.rb:31:13:31:20 | call to call_you | toplevel_self_singleton.rb:29:9:32:11 | call_you |
286287
unresolvedCall
@@ -372,7 +373,6 @@ unresolvedCall
372373
| toplevel_self_singleton.rb:8:1:16:3 | call to do_something |
373374
| toplevel_self_singleton.rb:10:9:10:27 | call to ab_singleton_method |
374375
| toplevel_self_singleton.rb:14:9:14:27 | call to ab_singleton_method |
375-
| toplevel_self_singleton.rb:18:12:22:1 | call to new |
376376
| toplevel_self_singleton.rb:20:9:20:27 | call to ab_singleton_method |
377377
privateMethod
378378
| calls.rb:1:1:3:3 | foo |

ruby/ql/test/library-tests/modules/methods.expected

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,18 @@ lookupMethod
599599
| toplevel_self_singleton.rb:2:5:5:7 | A::B | new | calls.rb:117:5:117:16 | new |
600600
| toplevel_self_singleton.rb:2:5:5:7 | A::B | puts | calls.rb:102:5:102:30 | puts |
601601
| toplevel_self_singleton.rb:2:5:5:7 | A::B | to_s | calls.rb:172:5:173:7 | to_s |
602+
| unresolved_subclass.rb:1:1:2:3 | ResolvableBaseClass | new | calls.rb:117:5:117:16 | new |
603+
| unresolved_subclass.rb:1:1:2:3 | ResolvableBaseClass | puts | calls.rb:102:5:102:30 | puts |
604+
| unresolved_subclass.rb:1:1:2:3 | ResolvableBaseClass | to_s | calls.rb:172:5:173:7 | to_s |
605+
| unresolved_subclass.rb:4:1:5:3 | UnresolvedNamespace::Subclass1 | new | calls.rb:117:5:117:16 | new |
606+
| unresolved_subclass.rb:4:1:5:3 | UnresolvedNamespace::Subclass1 | puts | calls.rb:102:5:102:30 | puts |
607+
| unresolved_subclass.rb:4:1:5:3 | UnresolvedNamespace::Subclass1 | to_s | calls.rb:172:5:173:7 | to_s |
608+
| unresolved_subclass.rb:7:1:8:3 | UnresolvedNamespace::Subclass2 | new | calls.rb:117:5:117:16 | new |
609+
| unresolved_subclass.rb:7:1:8:3 | UnresolvedNamespace::Subclass2 | puts | calls.rb:102:5:102:30 | puts |
610+
| unresolved_subclass.rb:7:1:8:3 | UnresolvedNamespace::Subclass2 | to_s | calls.rb:172:5:173:7 | to_s |
611+
| unresolved_subclass.rb:11:1:12:3 | UnresolvedNamespace::A | new | calls.rb:117:5:117:16 | new |
612+
| unresolved_subclass.rb:11:1:12:3 | UnresolvedNamespace::A | puts | calls.rb:102:5:102:30 | puts |
613+
| unresolved_subclass.rb:11:1:12:3 | UnresolvedNamespace::A | to_s | calls.rb:172:5:173:7 | to_s |
602614
enclosingMethod
603615
| calls.rb:2:5:2:14 | call to puts | calls.rb:1:1:3:3 | foo |
604616
| calls.rb:2:5:2:14 | self | calls.rb:1:1:3:3 | foo |

ruby/ql/test/library-tests/modules/modules.expected

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,18 @@ getModule
3535
| file://:0:0:0:0 | BasicObject |
3636
| file://:0:0:0:0 | Class |
3737
| file://:0:0:0:0 | Complex |
38+
| file://:0:0:0:0 | EsotericInstanceMethods |
3839
| file://:0:0:0:0 | FalseClass |
3940
| file://:0:0:0:0 | Float |
41+
| file://:0:0:0:0 | MyStruct |
4042
| file://:0:0:0:0 | NilClass |
4143
| file://:0:0:0:0 | Numeric |
4244
| file://:0:0:0:0 | Proc |
4345
| file://:0:0:0:0 | Rational |
46+
| file://:0:0:0:0 | Struct |
4447
| file://:0:0:0:0 | Symbol |
4548
| file://:0:0:0:0 | TrueClass |
49+
| file://:0:0:0:0 | UnresolvedNamespace |
4650
| hello.rb:1:1:8:3 | EnglishWords |
4751
| hello.rb:11:1:16:3 | Greeting |
4852
| hello.rb:18:1:22:3 | HelloWorld |
@@ -85,6 +89,10 @@ getModule
8589
| private.rb:96:1:102:3 | PrivateOverride2 |
8690
| toplevel_self_singleton.rb:2:5:5:7 | A::B |
8791
| toplevel_self_singleton.rb:24:1:34:3 | Good |
92+
| unresolved_subclass.rb:1:1:2:3 | ResolvableBaseClass |
93+
| unresolved_subclass.rb:4:1:5:3 | UnresolvedNamespace::Subclass1 |
94+
| unresolved_subclass.rb:7:1:8:3 | UnresolvedNamespace::Subclass2 |
95+
| unresolved_subclass.rb:11:1:12:3 | UnresolvedNamespace::A |
8896
getADeclaration
8997
| calls.rb:21:1:34:3 | M | calls.rb:21:1:34:3 | M |
9098
| calls.rb:43:1:58:3 | C | calls.rb:43:1:58:3 | C |
@@ -101,6 +109,7 @@ getADeclaration
101109
| calls.rb:115:1:118:3 | Object | modules_rec.rb:1:1:11:26 | modules_rec.rb |
102110
| calls.rb:115:1:118:3 | Object | private.rb:1:1:105:40 | private.rb |
103111
| calls.rb:115:1:118:3 | Object | toplevel_self_singleton.rb:1:1:34:4 | toplevel_self_singleton.rb |
112+
| calls.rb:115:1:118:3 | Object | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
104113
| calls.rb:120:1:123:3 | Hash | calls.rb:120:1:123:3 | Hash |
105114
| calls.rb:125:1:138:3 | Array | calls.rb:125:1:138:3 | Array |
106115
| calls.rb:165:1:169:3 | S | calls.rb:165:1:169:3 | S |
@@ -176,6 +185,10 @@ getADeclaration
176185
| toplevel_self_singleton.rb:2:5:5:7 | A::B | modules_rec.rb:4:1:5:3 | B |
177186
| toplevel_self_singleton.rb:2:5:5:7 | A::B | toplevel_self_singleton.rb:2:5:5:7 | B |
178187
| toplevel_self_singleton.rb:24:1:34:3 | Good | toplevel_self_singleton.rb:24:1:34:3 | Good |
188+
| unresolved_subclass.rb:1:1:2:3 | ResolvableBaseClass | unresolved_subclass.rb:1:1:2:3 | ResolvableBaseClass |
189+
| unresolved_subclass.rb:4:1:5:3 | UnresolvedNamespace::Subclass1 | unresolved_subclass.rb:4:1:5:3 | Subclass1 |
190+
| unresolved_subclass.rb:7:1:8:3 | UnresolvedNamespace::Subclass2 | unresolved_subclass.rb:7:1:8:3 | Subclass2 |
191+
| unresolved_subclass.rb:11:1:12:3 | UnresolvedNamespace::A | unresolved_subclass.rb:11:1:12:3 | A |
179192
getSuperClass
180193
| calls.rb:43:1:58:3 | C | calls.rb:115:1:118:3 | Object |
181194
| calls.rb:65:1:69:3 | D | calls.rb:43:1:58:3 | C |
@@ -231,6 +244,10 @@ getSuperClass
231244
| private.rb:82:1:94:3 | PrivateOverride1 | calls.rb:115:1:118:3 | Object |
232245
| private.rb:96:1:102:3 | PrivateOverride2 | private.rb:82:1:94:3 | PrivateOverride1 |
233246
| toplevel_self_singleton.rb:2:5:5:7 | A::B | calls.rb:115:1:118:3 | Object |
247+
| unresolved_subclass.rb:1:1:2:3 | ResolvableBaseClass | calls.rb:115:1:118:3 | Object |
248+
| unresolved_subclass.rb:4:1:5:3 | UnresolvedNamespace::Subclass1 | unresolved_subclass.rb:1:1:2:3 | ResolvableBaseClass |
249+
| unresolved_subclass.rb:7:1:8:3 | UnresolvedNamespace::Subclass2 | unresolved_subclass.rb:4:1:5:3 | UnresolvedNamespace::Subclass1 |
250+
| unresolved_subclass.rb:11:1:12:3 | UnresolvedNamespace::A | calls.rb:115:1:118:3 | Object |
234251
getAPrependedModule
235252
| calls.rb:115:1:118:3 | Object | calls.rb:171:1:174:3 | A |
236253
| calls.rb:171:1:174:3 | A | toplevel_self_singleton.rb:2:5:5:7 | A::B |
@@ -308,6 +325,11 @@ resolveConstantReadAccess
308325
| calls.rb:471:5:471:11 | Array | Array |
309326
| calls.rb:477:5:477:9 | Class | Class |
310327
| calls.rb:483:5:483:11 | Array | Array |
328+
| calls.rb:490:1:490:23 | EsotericInstanceMethods | EsotericInstanceMethods |
329+
| calls.rb:491:1:491:23 | EsotericInstanceMethods | EsotericInstanceMethods |
330+
| calls.rb:492:1:492:23 | EsotericInstanceMethods | EsotericInstanceMethods |
331+
| calls.rb:493:1:493:23 | EsotericInstanceMethods | EsotericInstanceMethods |
332+
| calls.rb:494:1:494:23 | EsotericInstanceMethods | EsotericInstanceMethods |
311333
| calls.rb:504:1:504:21 | ExtendSingletonMethod | ExtendSingletonMethod |
312334
| calls.rb:507:12:507:32 | ExtendSingletonMethod | ExtendSingletonMethod |
313335
| calls.rb:510:1:510:22 | ExtendSingletonMethod2 | ExtendSingletonMethod2 |
@@ -373,6 +395,14 @@ resolveConstantReadAccess
373395
| private.rb:100:7:100:22 | PrivateOverride1 | PrivateOverride1 |
374396
| private.rb:104:1:104:16 | PrivateOverride2 | PrivateOverride2 |
375397
| private.rb:105:1:105:16 | PrivateOverride2 | PrivateOverride2 |
398+
| toplevel_self_singleton.rb:18:12:18:17 | Struct | Struct |
399+
| unresolved_subclass.rb:4:7:4:25 | UnresolvedNamespace | UnresolvedNamespace |
400+
| unresolved_subclass.rb:4:40:4:58 | ResolvableBaseClass | ResolvableBaseClass |
401+
| unresolved_subclass.rb:7:7:7:25 | UnresolvedNamespace | UnresolvedNamespace |
402+
| unresolved_subclass.rb:7:40:7:58 | UnresolvedNamespace | UnresolvedNamespace |
403+
| unresolved_subclass.rb:7:40:7:69 | Subclass1 | UnresolvedNamespace::Subclass1 |
404+
| unresolved_subclass.rb:11:7:11:25 | UnresolvedNamespace | UnresolvedNamespace |
405+
| unresolved_subclass.rb:11:32:11:50 | UnresolvedNamespace | UnresolvedNamespace |
376406
resolveConstantWriteAccess
377407
| calls.rb:21:1:34:3 | M | M |
378408
| calls.rb:43:1:58:3 | C | C |
@@ -469,6 +499,10 @@ resolveConstantWriteAccess
469499
| toplevel_self_singleton.rb:2:5:5:7 | B | A::B |
470500
| toplevel_self_singleton.rb:18:1:18:8 | MyStruct | MyStruct |
471501
| toplevel_self_singleton.rb:24:1:34:3 | Good | Good |
502+
| unresolved_subclass.rb:1:1:2:3 | ResolvableBaseClass | ResolvableBaseClass |
503+
| unresolved_subclass.rb:4:1:5:3 | Subclass1 | UnresolvedNamespace::Subclass1 |
504+
| unresolved_subclass.rb:7:1:8:3 | Subclass2 | UnresolvedNamespace::Subclass2 |
505+
| unresolved_subclass.rb:11:1:12:3 | A | UnresolvedNamespace::A |
472506
enclosingModule
473507
| calls.rb:1:1:3:3 | foo | calls.rb:1:1:616:32 | calls.rb |
474508
| calls.rb:2:5:2:14 | call to puts | calls.rb:1:1:616:32 | calls.rb |
@@ -1792,3 +1826,15 @@ enclosingModule
17921826
| toplevel_self_singleton.rb:30:13:30:19 | self | toplevel_self_singleton.rb:25:5:33:7 | class << ... |
17931827
| toplevel_self_singleton.rb:31:13:31:20 | call to call_you | toplevel_self_singleton.rb:25:5:33:7 | class << ... |
17941828
| toplevel_self_singleton.rb:31:13:31:20 | self | toplevel_self_singleton.rb:25:5:33:7 | class << ... |
1829+
| unresolved_subclass.rb:1:1:2:3 | ResolvableBaseClass | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1830+
| unresolved_subclass.rb:4:1:5:3 | Subclass1 | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1831+
| unresolved_subclass.rb:4:7:4:25 | UnresolvedNamespace | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1832+
| unresolved_subclass.rb:4:40:4:58 | ResolvableBaseClass | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1833+
| unresolved_subclass.rb:7:1:8:3 | Subclass2 | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1834+
| unresolved_subclass.rb:7:7:7:25 | UnresolvedNamespace | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1835+
| unresolved_subclass.rb:7:40:7:58 | UnresolvedNamespace | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1836+
| unresolved_subclass.rb:7:40:7:69 | Subclass1 | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1837+
| unresolved_subclass.rb:11:1:12:3 | A | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1838+
| unresolved_subclass.rb:11:7:11:25 | UnresolvedNamespace | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1839+
| unresolved_subclass.rb:11:32:11:50 | UnresolvedNamespace | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |
1840+
| unresolved_subclass.rb:11:32:11:53 | B | unresolved_subclass.rb:1:1:12:4 | unresolved_subclass.rb |

ruby/ql/test/library-tests/modules/superclasses.expected

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
#-----| Class
22
#-----| -> Module
33

4+
#-----| EsotericInstanceMethods
5+
6+
#-----| MyStruct
7+
8+
#-----| Struct
9+
10+
#-----| UnresolvedNamespace
11+
412
#-----| BasicObject
513

614
#-----| Complex
@@ -230,3 +238,16 @@ toplevel_self_singleton.rb:
230238
#-----| -> Object
231239

232240
# 24| Good
241+
242+
unresolved_subclass.rb:
243+
# 1| ResolvableBaseClass
244+
#-----| -> Object
245+
246+
# 4| UnresolvedNamespace::Subclass1
247+
#-----| -> ResolvableBaseClass
248+
249+
# 7| UnresolvedNamespace::Subclass2
250+
#-----| -> UnresolvedNamespace::Subclass1
251+
252+
# 11| UnresolvedNamespace::A
253+
#-----| -> Object
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class ResolvableBaseClass
2+
end
3+
4+
class UnresolvedNamespace::Subclass1 < ResolvableBaseClass
5+
end
6+
7+
class UnresolvedNamespace::Subclass2 < UnresolvedNamespace::Subclass1
8+
end
9+
10+
# Ensure Object is a transitive superclass of this
11+
class UnresolvedNamespace::A < UnresolvedNamespace::B
12+
end

0 commit comments

Comments
 (0)