Compare commits

..

2 Commits

Author SHA1 Message Date
idrissrio
8cc361bf35 C++: accept test results 2026-05-28 16:14:36 +02:00
idrissrio
749a40153d C++: accept test results 2026-05-28 16:14:34 +02:00
142 changed files with 7081 additions and 11088 deletions

View File

@@ -30,6 +30,8 @@ class Options extends string {
predicate overrideReturnsNull(Call call) {
// Used in CVS:
call.(FunctionCall).getTarget().hasGlobalName("Xstrdup")
or
CustomOptions::overrideReturnsNull(call) // old Options.qll
}
/**
@@ -43,6 +45,8 @@ class Options extends string {
// Used in CVS:
call.(FunctionCall).getTarget().hasGlobalName("Xstrdup") and
nullValue(call.getArgument(0))
or
CustomOptions::returnsNull(call) // old Options.qll
}
/**
@@ -61,6 +65,8 @@ class Options extends string {
f.hasGlobalOrStdName([
"exit", "_exit", "_Exit", "abort", "__assert_fail", "longjmp", "__builtin_unreachable"
])
or
CustomOptions::exits(f) // old Options.qll
}
/**
@@ -73,7 +79,8 @@ class Options extends string {
* runtime, the program's behavior is undefined)
*/
predicate exprExits(Expr e) {
e.(AssumeExpr).getChild(0).(CompileTimeConstantInt).getIntValue() = 0
e.(AssumeExpr).getChild(0).(CompileTimeConstantInt).getIntValue() = 0 or
CustomOptions::exprExits(e) // old Options.qll
}
/**
@@ -81,7 +88,10 @@ class Options extends string {
*
* By default holds only for `fgets`.
*/
predicate alwaysCheckReturnValue(Function f) { f.hasGlobalOrStdName("fgets") }
predicate alwaysCheckReturnValue(Function f) {
f.hasGlobalOrStdName("fgets") or
CustomOptions::alwaysCheckReturnValue(f) // old Options.qll
}
/**
* Holds if it is reasonable to ignore the return value of function
@@ -97,6 +107,8 @@ class Options extends string {
// common way of sleeping using select:
fc.getTarget().hasGlobalName("select") and
fc.getArgument(0).getValue() = "0"
or
CustomOptions::okToIgnoreReturnValue(fc) // old Options.qll
}
}

View File

@@ -98,3 +98,57 @@ class CustomMutexType extends MutexType {
*/
override predicate unlockAccess(FunctionCall fc, Expr arg) { none() }
}
/**
* DEPRECATED: customize `CustomOptions.overrideReturnsNull` instead.
*
* This predicate is required to support backwards compatibility for
* older `Options.qll` files. It should not be removed or modified by
* end users.
*/
predicate overrideReturnsNull(Call call) { none() }
/**
* DEPRECATED: customize `CustomOptions.returnsNull` instead.
*
* This predicate is required to support backwards compatibility for
* older `Options.qll` files. It should not be removed or modified by
* end users.
*/
predicate returnsNull(Call call) { none() }
/**
* DEPRECATED: customize `CustomOptions.exits` instead.
*
* This predicate is required to support backwards compatibility for
* older `Options.qll` files. It should not be removed or modified by
* end users.
*/
predicate exits(Function f) { none() }
/**
* DEPRECATED: customize `CustomOptions.exprExits` instead.
*
* This predicate is required to support backwards compatibility for
* older `Options.qll` files. It should not be removed or modified by
* end users.
*/
predicate exprExits(Expr e) { none() }
/**
* DEPRECATED: customize `CustomOptions.alwaysCheckReturnValue` instead.
*
* This predicate is required to support backwards compatibility for
* older `Options.qll` files. It should not be removed or modified by
* end users.
*/
predicate alwaysCheckReturnValue(Function f) { none() }
/**
* DEPRECATED: customize `CustomOptions.okToIgnoreReturnValue` instead.
*
* This predicate is required to support backwards compatibility for
* older `Options.qll` files. It should not be removed or modified by
* end users.
*/
predicate okToIgnoreReturnValue(FunctionCall fc) { none() }

View File

@@ -1,15 +0,0 @@
---
category: breaking
---
* Removed the deprecated `overrideReturnsNull` predicate from `Options.qll`. Use `CustomOptions.overrideReturnsNull` instead.
* Removed the deprecated `returnsNull` predicate from `Options.qll`. Use `CustomOptions.returnsNull` instead.
* Removed the deprecated `exits` predicate from `Options.qll`. Use `CustomOptions.exits` instead.
* Removed the deprecated `exprExits` predicate from `Options.qll`. Use `CustomOptions.exprExits` instead.
* Removed the deprecated `alwaysCheckReturnValue` predicate from `Options.qll`. Use `CustomOptions.alwaysCheckReturnValue` instead.
* Removed the deprecated `okToIgnoreReturnValue` predicate from `Options.qll`. Use `CustomOptions.okToIgnoreReturnValue` instead.
* Removed the deprecated `semmle.code.cpp.Member`. Import `semmle.code.cpp.Element` and/or `semmle.code.cpp.Type` directly.
* Removed the deprecated `UnknownDefaultLocation` class. Use `UnknownLocation` instead.
* Removed the deprecated `UnknownExprLocation` class. Use `UnknownLocation` instead.
* Removed the deprecated `UnknownStmtLocation` class. Use `UnknownLocation` instead.
* Removed the deprecated `TemplateParameter` class. Use `TypeTemplateParameter` instead.
* Support for class resolution across link targets has been removed for databases which were created with CodeQL versions before 1.23.0.

View File

@@ -32,6 +32,7 @@ import semmle.code.cpp.Class
import semmle.code.cpp.Struct
import semmle.code.cpp.Union
import semmle.code.cpp.Enum
import semmle.code.cpp.Member
import semmle.code.cpp.Field
import semmle.code.cpp.Function
import semmle.code.cpp.MemberFunction

View File

@@ -148,3 +148,28 @@ class UnknownLocation extends Location {
this.getFile().getAbsolutePath() = "" and locations_default(this, _, 0, 0, 0, 0)
}
}
/**
* A dummy location which is used when something doesn't have a location in
* the source code but needs to have a `Location` associated with it.
*
* DEPRECATED: use `UnknownLocation`
*/
deprecated class UnknownDefaultLocation extends UnknownLocation { }
/**
* A dummy location which is used when an expression doesn't have a
* location in the source code but needs to have a `Location` associated
* with it.
*
* DEPRECATED: use `UnknownLocation`
*/
deprecated class UnknownExprLocation extends UnknownLocation { }
/**
* A dummy location which is used when a statement doesn't have a location
* in the source code but needs to have a `Location` associated with it.
*
* DEPRECATED: use `UnknownLocation`
*/
deprecated class UnknownStmtLocation extends UnknownLocation { }

View File

@@ -0,0 +1,6 @@
/**
* DEPRECATED: import `semmle.code.cpp.Element` and/or `semmle.code.cpp.Type` directly as required.
*/
import semmle.code.cpp.Element
import semmle.code.cpp.Type

View File

@@ -35,6 +35,13 @@ class NonTypeTemplateParameter extends Literal, TemplateParameterImpl {
override string getAPrimaryQlClass() { result = "NonTypeTemplateParameter" }
}
/**
* A C++ `typename` (or `class`) template parameter.
*
* DEPRECATED: Use `TypeTemplateParameter` instead.
*/
deprecated class TemplateParameter = TypeTemplateParameter;
/**
* A C++ `typename` (or `class`) template parameter.
*

View File

@@ -1,5 +1,59 @@
import semmle.code.cpp.Type
/** For upgraded databases without mangled name info. */
pragma[noinline]
private string getTopLevelClassName(@usertype c) {
not mangled_name(_, _, _) and
isClass(c) and
usertypes(c, result, _) and
not namespacembrs(_, c) and // not in a namespace
not member(_, _, c) and // not in some structure
not class_instantiation(c, _) // not a template instantiation
}
/**
* For upgraded databases without mangled name info.
* Holds if `d` is a unique complete class named `name`.
*/
pragma[noinline]
private predicate existsCompleteWithName(string name, @usertype d) {
not mangled_name(_, _, _) and
is_complete(d) and
name = getTopLevelClassName(d) and
onlyOneCompleteClassExistsWithName(name)
}
/** For upgraded databases without mangled name info. */
pragma[noinline]
private predicate onlyOneCompleteClassExistsWithName(string name) {
not mangled_name(_, _, _) and
strictcount(@usertype c | is_complete(c) and getTopLevelClassName(c) = name) = 1
}
/**
* For upgraded databases without mangled name info.
* Holds if `c` is an incomplete class named `name`.
*/
pragma[noinline]
private predicate existsIncompleteWithName(string name, @usertype c) {
not mangled_name(_, _, _) and
not is_complete(c) and
name = getTopLevelClassName(c)
}
/**
* For upgraded databases without mangled name info.
* Holds if `c` is an incomplete class, and there exists a unique complete class `d`
* with the same name.
*/
private predicate oldHasCompleteTwin(@usertype c, @usertype d) {
not mangled_name(_, _, _) and
exists(string name |
existsIncompleteWithName(name, c) and
existsCompleteWithName(name, d)
)
}
pragma[noinline]
private @mangledname getClassMangledName(@usertype c) {
isClass(c) and
@@ -49,7 +103,10 @@ private module Cached {
@usertype resolveClass(@usertype c) {
hasCompleteTwin(c, result)
or
oldHasCompleteTwin(c, result)
or
not hasCompleteTwin(c, _) and
not oldHasCompleteTwin(c, _) and
result = c
}

View File

@@ -1 +1,2 @@
| common.h:5:12:5:18 | myArray | int[1] |
| common.h:5:12:5:18 | myArray | int[8] |

View File

@@ -1,6 +1,7 @@
| extracted_once.h:11:14:11:22 | intMember | file1.c:6:18:6:25 | intAlias | 1 |
| extracted_once.h:12:23:12:40 | qualifiedIntMember | file1.c:7:13:7:29 | qualifiedIntAlias | 1 |
| extracted_once.h:16:28:16:32 | param | file1.c:6:18:6:25 | intAlias | 1 |
| extracted_once.h:11:14:11:22 | intMember | file://:0:0:0:0 | int | 1 |
| extracted_once.h:12:23:12:40 | qualifiedIntMember | file://:0:0:0:0 | int | 1 |
| extracted_once.h:16:28:16:32 | param | file1.c:6:18:6:25 | intAlias | 2 |
| extracted_once.h:16:28:16:32 | param | file2.c:1:13:1:20 | intAlias | 2 |
| extracted_twice.h:14:14:14:22 | intMember | file://:0:0:0:0 | int | 1 |
| extracted_twice.h:15:23:15:40 | qualifiedIntMember | file://:0:0:0:0 | int | 1 |
| extracted_twice.h:24:17:24:28 | structMember | file1.c:8:33:8:43 | structAlias | 1 |

View File

@@ -1,5 +1,6 @@
| a.c:4:5:4:6 | definition of is | array of {int} | 1 |
| a.h:2:12:2:13 | declaration of is | array of 4 {int} | 1 |
| a.h:2:12:2:13 | declaration of is | array of 4 {int} | 1 |
| c.c:2:5:2:6 | definition of js | array of {int} | 1 |
| c.c:4:5:4:6 | definition of ks | array of 4 {int} | 1 |
| c.c:6:5:6:6 | definition of ls | array of 4 {int} | 1 |

View File

@@ -664,7 +664,7 @@ namespace Semmle.Extraction.CSharp
// Find the (possibly unbound) original extension method that maps to this implementation (if any).
var unboundDeclaration = extensions.SelectMany(e => e.GetMembers())
.OfType<IMethodSymbol>()
.FirstOrDefault(m => SymbolEqualityComparer.Default.Equals(m.AssociatedExtensionImplementation?.ConstructedFrom, method.ConstructedFrom));
.FirstOrDefault(m => SymbolEqualityComparer.Default.Equals(m.AssociatedExtensionImplementation, method.ConstructedFrom));
var isFullyConstructed = method.IsBoundGenericMethod();
if (isFullyConstructed && unboundDeclaration?.ContainingType is INamedTypeSymbol extensionType)

View File

@@ -69,7 +69,6 @@ namespace Semmle.Extraction.CSharp.Entities
}
Overrides(trapFile);
ExtractRefReturn(trapFile, Symbol, this);
if (Symbol.FromSource() && !HasBody)
{

View File

@@ -4,7 +4,7 @@ source https://api.nuget.org/v3/index.json
# behave like nuget in choosing transitive dependency versions
strategy: max
nuget Basic.CompilerLog.Util 0.9.39
nuget Basic.CompilerLog.Util 0.9.25
nuget Mono.Posix.NETStandard
nuget Newtonsoft.Json
nuget NuGet.Versioning
@@ -12,7 +12,7 @@ nuget xunit
nuget xunit.runner.visualstudio
nuget xunit.runner.utility
nuget Microsoft.NET.Test.Sdk
nuget Microsoft.CodeAnalysis.CSharp 5.3.0
nuget Microsoft.CodeAnalysis 5.3.0
nuget Microsoft.Build 18.6.3
nuget Microsoft.CodeAnalysis.CSharp 5.0.0
nuget Microsoft.CodeAnalysis 5.0.0
nuget Microsoft.Build 18.0.2
nuget Microsoft.VisualStudio.SolutionPersistence

100
csharp/paket.lock generated
View File

@@ -3,42 +3,45 @@ STRATEGY: MAX
RESTRICTION: == net10.0
NUGET
remote: https://api.nuget.org/v3/index.json
Basic.CompilerLog.Util (0.9.39)
Basic.CompilerLog.Util (0.9.25)
MessagePack (>= 3.1.4)
Microsoft.Bcl.Memory (>= 10.0.7)
Microsoft.Bcl.Memory (>= 9.0.10)
Microsoft.CodeAnalysis (>= 4.8)
Microsoft.CodeAnalysis.CSharp (>= 4.8)
Microsoft.CodeAnalysis.VisualBasic (>= 4.8)
Microsoft.Extensions.ObjectPool (>= 10.0.7)
MSBuild.StructuredLogger (>= 2.3.178)
Microsoft.Extensions.ObjectPool (>= 9.0.10)
MSBuild.StructuredLogger (>= 2.3.71)
NaturalSort.Extension (>= 4.4)
NuGet.Versioning (>= 6.14)
Humanizer.Core (3.0.10)
MessagePack (3.1.6)
MessagePack.Annotations (>= 3.1.6)
MessagePackAnalyzer (>= 3.1.6)
MessagePack (3.1.4)
MessagePack.Annotations (>= 3.1.4)
MessagePackAnalyzer (>= 3.1.4)
Microsoft.NET.StringTools (>= 17.11.4)
MessagePack.Annotations (3.1.6)
MessagePackAnalyzer (3.1.6)
MessagePack.Annotations (3.1.4)
MessagePackAnalyzer (3.1.4)
Microsoft.Bcl.AsyncInterfaces (10.0.8)
Microsoft.Bcl.Memory (10.0.8)
Microsoft.Build (18.6.3)
Microsoft.Build.Framework (>= 18.6.3)
System.Configuration.ConfigurationManager (>= 10.0.3)
System.Diagnostics.EventLog (>= 10.0.3)
System.Reflection.MetadataLoadContext (>= 10.0.3)
System.Security.Cryptography.ProtectedData (>= 10.0.3)
Microsoft.Build.Framework (18.6.3)
Microsoft.NET.StringTools (>= 18.6.3)
Microsoft.Build.Utilities.Core (18.6.3)
Microsoft.Build.Framework (>= 18.6.3)
System.Configuration.ConfigurationManager (>= 10.0.3)
System.Diagnostics.EventLog (>= 10.0.3)
System.Security.Cryptography.ProtectedData (>= 10.0.3)
Microsoft.CodeAnalysis (5.3)
Microsoft.Build (18.0.2)
Microsoft.Build.Framework (>= 18.0.2)
Microsoft.NET.StringTools (>= 18.0.2)
System.Configuration.ConfigurationManager (>= 9.0)
System.Diagnostics.EventLog (>= 9.0)
System.Reflection.MetadataLoadContext (>= 9.0)
System.Security.Cryptography.ProtectedData (>= 9.0.6)
Microsoft.Build.Framework (18.4)
Microsoft.Build.Utilities.Core (18.4)
Microsoft.Build.Framework (>= 18.4)
Microsoft.NET.StringTools (>= 18.4)
System.Configuration.ConfigurationManager (>= 10.0.1)
System.Diagnostics.EventLog (>= 10.0.1)
System.Security.Cryptography.ProtectedData (>= 10.0.1)
Microsoft.CodeAnalysis (5.0)
Humanizer.Core (>= 2.14.1)
Microsoft.Bcl.AsyncInterfaces (>= 9.0)
Microsoft.CodeAnalysis.Analyzers (>= 5.3.0-2.25625.1)
Microsoft.CodeAnalysis.CSharp.Workspaces (5.3)
Microsoft.CodeAnalysis.VisualBasic.Workspaces (5.3)
Microsoft.CodeAnalysis.Analyzers (>= 3.11)
Microsoft.CodeAnalysis.CSharp.Workspaces (5.0)
Microsoft.CodeAnalysis.VisualBasic.Workspaces (5.0)
System.Buffers (>= 4.6)
System.Collections.Immutable (>= 9.0)
System.Composition (>= 9.0)
@@ -51,36 +54,36 @@ NUGET
System.Threading.Channels (>= 8.0)
System.Threading.Tasks.Extensions (>= 4.6)
Microsoft.CodeAnalysis.Analyzers (5.3)
Microsoft.CodeAnalysis.Common (5.3)
Microsoft.CodeAnalysis.Analyzers (>= 5.3.0-2.25625.1)
Microsoft.CodeAnalysis.CSharp (5.3)
Microsoft.CodeAnalysis.Analyzers (>= 5.3.0-2.25625.1)
Microsoft.CodeAnalysis.Common (5.3)
Microsoft.CodeAnalysis.CSharp.Workspaces (5.3)
Microsoft.CodeAnalysis.Common (5.0)
Microsoft.CodeAnalysis.Analyzers (>= 3.11)
Microsoft.CodeAnalysis.CSharp (5.0)
Microsoft.CodeAnalysis.Analyzers (>= 3.11)
Microsoft.CodeAnalysis.Common (5.0)
Microsoft.CodeAnalysis.CSharp.Workspaces (5.0)
Humanizer.Core (>= 2.14.1)
Microsoft.CodeAnalysis.Analyzers (>= 5.3.0-2.25625.1)
Microsoft.CodeAnalysis.Common (5.3)
Microsoft.CodeAnalysis.CSharp (5.3)
Microsoft.CodeAnalysis.Workspaces.Common (5.3)
Microsoft.CodeAnalysis.Analyzers (>= 3.11)
Microsoft.CodeAnalysis.Common (5.0)
Microsoft.CodeAnalysis.CSharp (5.0)
Microsoft.CodeAnalysis.Workspaces.Common (5.0)
System.Composition (>= 9.0)
Microsoft.CodeAnalysis.VisualBasic (5.3)
Microsoft.CodeAnalysis.Analyzers (>= 5.3.0-2.25625.1)
Microsoft.CodeAnalysis.Common (5.3)
Microsoft.CodeAnalysis.VisualBasic.Workspaces (5.3)
Microsoft.CodeAnalysis.VisualBasic (5.0)
Microsoft.CodeAnalysis.Analyzers (>= 3.11)
Microsoft.CodeAnalysis.Common (5.0)
Microsoft.CodeAnalysis.VisualBasic.Workspaces (5.0)
Humanizer.Core (>= 2.14.1)
Microsoft.CodeAnalysis.Analyzers (>= 5.3.0-2.25625.1)
Microsoft.CodeAnalysis.Common (5.3)
Microsoft.CodeAnalysis.VisualBasic (5.3)
Microsoft.CodeAnalysis.Workspaces.Common (5.3)
Microsoft.CodeAnalysis.Analyzers (>= 3.11)
Microsoft.CodeAnalysis.Common (5.0)
Microsoft.CodeAnalysis.VisualBasic (5.0)
Microsoft.CodeAnalysis.Workspaces.Common (5.0)
System.Composition (>= 9.0)
Microsoft.CodeAnalysis.Workspaces.Common (5.3)
Microsoft.CodeAnalysis.Workspaces.Common (5.0)
Humanizer.Core (>= 2.14.1)
Microsoft.CodeAnalysis.Analyzers (>= 5.3.0-2.25625.1)
Microsoft.CodeAnalysis.Common (5.3)
Microsoft.CodeAnalysis.Analyzers (>= 3.11)
Microsoft.CodeAnalysis.Common (5.0)
System.Composition (>= 9.0)
Microsoft.CodeCoverage (18.5.1)
Microsoft.Extensions.ObjectPool (10.0.8)
Microsoft.NET.StringTools (18.6.3)
Microsoft.NET.StringTools (18.4)
Microsoft.NET.Test.Sdk (18.5.1)
Microsoft.CodeCoverage (>= 18.5.1)
Microsoft.TestPlatform.TestHost (>= 18.5.1)
@@ -94,6 +97,7 @@ NUGET
MSBuild.StructuredLogger (2.3.204)
Microsoft.Build.Framework (>= 17.5)
Microsoft.Build.Utilities.Core (>= 17.5)
NaturalSort.Extension (4.4.1)
Newtonsoft.Json (13.0.4)
NuGet.Versioning (7.6)
System.Buffers (4.6.1)

31
csharp/paket.main.bzl generated

File diff suppressed because one or more lines are too long

View File

@@ -22,6 +22,7 @@
| [...]/csharp/tools/[...]/Microsoft.Win32.Primitives.dll |
| [...]/csharp/tools/[...]/Microsoft.Win32.Registry.dll |
| [...]/csharp/tools/[...]/Mono.Posix.NETStandard.dll |
| [...]/csharp/tools/[...]/NaturalSort.Extension.dll |
| [...]/csharp/tools/[...]/Newtonsoft.Json.dll |
| [...]/csharp/tools/[...]/NuGet.Versioning.dll |
| [...]/csharp/tools/[...]/StructuredLogger.dll |

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Improved call target resolution for ref-return properties and indexers.

View File

@@ -766,16 +766,7 @@ class PropertyCall extends AccessorCall, PropertyAccessExpr {
}
override Accessor getWriteTarget() {
this instanceof AssignableWrite and
exists(Property p | p = this.getProperty() |
result = p.getSetter()
or
result =
any(Getter g |
g = p.getGetter() and
g.getAnnotatedReturnType().isRef()
)
)
this instanceof AssignableWrite and result = this.getProperty().getSetter()
}
override Expr getArgument(int i) {
@@ -810,16 +801,7 @@ class IndexerCall extends AccessorCall, IndexerAccessExpr {
}
override Accessor getWriteTarget() {
this instanceof AssignableWrite and
exists(Indexer i | i = this.getIndexer() |
result = i.getSetter()
or
result =
any(Getter g |
g = i.getGetter() and
g.getAnnotatedReturnType().isRef()
)
)
this instanceof AssignableWrite and result = this.getIndexer().getSetter()
}
override Expr getArgument(int i) {

View File

@@ -227,7 +227,7 @@ returnTypes
| NullableRefTypes.cs:107:26:107:36 | ReturnsRef5 | readonly MyClass! |
| NullableRefTypes.cs:108:26:108:36 | ReturnsRef6 | readonly MyClass! |
| NullableRefTypes.cs:110:10:110:20 | Parameters1 | Void! |
| NullableRefTypes.cs:113:32:113:44 | get_RefProperty | ref MyClass! |
| NullableRefTypes.cs:113:32:113:44 | get_RefProperty | MyClass! |
| NullableRefTypes.cs:116:7:116:23 | <object initializer> | Void |
| NullableRefTypes.cs:116:7:116:23 | ToStringWithTypes | Void! |
| NullableRefTypes.cs:136:7:136:24 | <object initializer> | Void |

View File

@@ -1,4 +1,4 @@
class SBCS
class SBCS
{
string sbcs = "<22>";
string sbcs = "<22>";
}

View File

@@ -1,4 +0,0 @@
| indexers.cs:24:21:24:24 | Item | indexers.cs:62:22:62:29 | access to indexer | indexers.cs:26:13:26:15 | get_Item |
| indexers.cs:24:21:24:24 | Item | indexers.cs:65:25:65:32 | access to indexer | indexers.cs:34:13:34:15 | set_Item |
| indexers.cs:143:24:143:27 | Item | indexers.cs:156:13:156:16 | access to indexer | indexers.cs:145:13:145:15 | get_Item |
| indexers.cs:143:24:143:27 | Item | indexers.cs:157:21:157:24 | access to indexer | indexers.cs:145:13:145:15 | get_Item |

View File

@@ -1,8 +0,0 @@
import csharp
from IndexerCall ic, Indexer i, Accessor target
where
ic.getIndexer() = i and
ic.getTarget() = target and
i.fromSource()
select i, ic, target

View File

@@ -360,57 +360,3 @@ indexers.cs:
# 130| 4: [BlockStmt] {...}
# 130| 0: [ReturnStmt] return ...;
# 130| 0: [IntLiteral] 0
# 134| 5: [RefStruct] S
# 136| 6: [Field] x
# 136| -1: [TypeMention] int
# 138| 7: [InstanceConstructor] S
#-----| 2: (Parameters)
# 138| 0: [Parameter] v
# 138| -1: [TypeMention] int
# 139| 4: [BlockStmt] {...}
# 140| 0: [ExprStmt] ...;
# 140| 0: [AssignExpr] ... = ...
# 140| 0: [FieldAccess] access to field x
# 140| 1: [RefExpr] ref ...
# 140| 0: [ParameterAccess] access to parameter v
# 143| 8: [Indexer] Item
# 143| -1: [TypeMention] int
#-----| 1: (Parameters)
# 143| 0: [Parameter] i
# 143| -1: [TypeMention] int
# 145| 3: [Getter] get_Item
#-----| 2: (Parameters)
# 143| 0: [Parameter] i
# 145| 4: [BlockStmt] {...}
# 145| 0: [ReturnStmt] return ...;
# 145| 0: [RefExpr] ref ...
# 145| 0: [FieldAccess] access to field x
# 149| 6: [Class] TestRefReturns
# 151| 6: [Method] M
# 151| -1: [TypeMention] Void
# 152| 4: [BlockStmt] {...}
# 153| 0: [LocalVariableDeclStmt] ... ...;
# 153| 0: [LocalVariableDeclAndInitExpr] Int32 a = ...
# 153| -1: [TypeMention] int
# 153| 0: [LocalVariableAccess] access to local variable a
# 153| 1: [IntLiteral] 0
# 155| 1: [LocalVariableDeclStmt] ... ...;
# 155| 0: [LocalVariableDeclAndInitExpr] S s = ...
# 155| -1: [TypeMention] S
# 155| 0: [LocalVariableAccess] access to local variable s
# 155| 1: [ObjectCreation] object creation of type S
# 155| -1: [TypeMention] S
# 155| 0: [LocalVariableAccess] access to local variable a
# 156| 2: [ExprStmt] ...;
# 156| 0: [AssignExpr] ... = ...
# 156| 0: [IndexerCall] access to indexer
# 156| -1: [LocalVariableAccess] access to local variable s
# 156| 0: [IntLiteral] 0
# 156| 1: [IntLiteral] 1
# 157| 3: [LocalVariableDeclStmt] ... ...;
# 157| 0: [LocalVariableDeclAndInitExpr] Int32 x = ...
# 157| -1: [TypeMention] int
# 157| 0: [LocalVariableAccess] access to local variable x
# 157| 1: [IndexerCall] access to indexer
# 157| -1: [LocalVariableAccess] access to local variable s
# 157| 0: [IntLiteral] 0

View File

@@ -130,31 +130,4 @@ namespace Indexers
get { return 0; }
}
}
public ref struct S
{
private ref int x;
public S(ref int v)
{
x = ref v;
}
public ref int this[int i]
{
get { return ref x; }
}
}
public class TestRefReturns
{
public void M()
{
int a = 0;
S s = new S(ref a);
s[0] = 1;
var x = s[0];
}
}
}

View File

@@ -246,50 +246,3 @@ properties.cs:
# 133| 0: [FieldAccess] access to field Prop.field
# 133| 1: [ParameterAccess] access to parameter value
# 130| 7: [Field] Prop.field
# 137| 11: [RefStruct] S
# 139| 6: [Field] x
# 139| -1: [TypeMention] int
# 141| 7: [InstanceConstructor] S
#-----| 2: (Parameters)
# 141| 0: [Parameter] v
# 141| -1: [TypeMention] int
# 142| 4: [BlockStmt] {...}
# 143| 0: [ExprStmt] ...;
# 143| 0: [AssignExpr] ... = ...
# 143| 0: [FieldAccess] access to field x
# 143| 1: [RefExpr] ref ...
# 143| 0: [ParameterAccess] access to parameter v
# 146| 8: [Property] Prop
# 146| -1: [TypeMention] int
# 148| 3: [Getter] get_Prop
# 148| 4: [BlockStmt] {...}
# 148| 0: [ReturnStmt] return ...;
# 148| 0: [RefExpr] ref ...
# 148| 0: [FieldAccess] access to field x
# 152| 12: [Class] TestRefReturns
# 154| 6: [Method] M
# 154| -1: [TypeMention] Void
# 155| 4: [BlockStmt] {...}
# 156| 0: [LocalVariableDeclStmt] ... ...;
# 156| 0: [LocalVariableDeclAndInitExpr] Int32 a = ...
# 156| -1: [TypeMention] int
# 156| 0: [LocalVariableAccess] access to local variable a
# 156| 1: [IntLiteral] 0
# 158| 1: [LocalVariableDeclStmt] ... ...;
# 158| 0: [LocalVariableDeclAndInitExpr] S s = ...
# 158| -1: [TypeMention] S
# 158| 0: [LocalVariableAccess] access to local variable s
# 158| 1: [ObjectCreation] object creation of type S
# 158| -1: [TypeMention] S
# 158| 0: [LocalVariableAccess] access to local variable a
# 159| 2: [ExprStmt] ...;
# 159| 0: [AssignExpr] ... = ...
# 159| 0: [PropertyCall] access to property Prop
# 159| -1: [LocalVariableAccess] access to local variable s
# 159| 1: [IntLiteral] 1
# 160| 3: [LocalVariableDeclStmt] ... ...;
# 160| 0: [LocalVariableDeclAndInitExpr] Int32 x = ...
# 160| -1: [TypeMention] int
# 160| 0: [LocalVariableAccess] access to local variable x
# 160| 1: [PropertyCall] access to property Prop
# 160| -1: [LocalVariableAccess] access to local variable s

View File

@@ -1,6 +1,5 @@
| Prop.field |
| caption |
| next |
| x |
| y |
| z |

View File

@@ -1,8 +0,0 @@
| properties.cs:12:23:12:29 | Caption | properties.cs:29:13:29:28 | access to property Caption | properties.cs:17:13:17:15 | set_Caption |
| properties.cs:12:23:12:29 | Caption | properties.cs:30:24:30:39 | access to property Caption | properties.cs:15:13:15:15 | get_Caption |
| properties.cs:57:20:57:20 | X | properties.cs:61:13:61:13 | access to property X | properties.cs:57:37:57:39 | set_X |
| properties.cs:58:20:58:20 | Y | properties.cs:62:13:62:13 | access to property Y | properties.cs:58:37:58:39 | set_Y |
| properties.cs:70:28:70:28 | X | properties.cs:82:46:82:51 | access to property X | properties.cs:70:32:70:34 | get_X |
| properties.cs:71:28:71:28 | Y | properties.cs:83:39:83:44 | access to property Y | properties.cs:74:13:74:15 | set_Y |
| properties.cs:146:24:146:27 | Prop | properties.cs:159:13:159:18 | access to property Prop | properties.cs:148:13:148:15 | get_Prop |
| properties.cs:146:24:146:27 | Prop | properties.cs:160:21:160:26 | access to property Prop | properties.cs:148:13:148:15 | get_Prop |

View File

@@ -1,8 +0,0 @@
import csharp
from PropertyCall pc, Property p, Accessor target
where
pc.getProperty() = p and
pc.getTarget() = target and
p.fromSource()
select p, pc, target

View File

@@ -133,31 +133,4 @@ namespace Properties
set { field = value; }
}
}
public ref struct S
{
private ref int x;
public S(ref int v)
{
x = ref v;
}
public ref int Prop
{
get { return ref x; }
}
}
public class TestRefReturns
{
public void M()
{
int a = 0;
S s = new S(ref a);
s.Prop = 1;
var x = s.Prop;
}
}
}

View File

@@ -1,2 +1,3 @@
| Quality.cs:26:19:26:26 | access to indexer | Call without target $@. | Quality.cs:26:19:26:26 | access to indexer | access to indexer |
| Quality.cs:29:21:29:27 | access to indexer | Call without target $@. | Quality.cs:29:21:29:27 | access to indexer | access to indexer |
| Quality.cs:32:9:32:21 | access to indexer | Call without target $@. | Quality.cs:32:9:32:21 | access to indexer | access to indexer |

View File

@@ -9,5 +9,6 @@
| Quality.cs:23:9:23:30 | delegate call | Call without target $@. | Quality.cs:23:9:23:30 | delegate call | delegate call |
| Quality.cs:26:19:26:26 | access to indexer | Call without target $@. | Quality.cs:26:19:26:26 | access to indexer | access to indexer |
| Quality.cs:29:21:29:27 | access to indexer | Call without target $@. | Quality.cs:29:21:29:27 | access to indexer | access to indexer |
| Quality.cs:32:9:32:21 | access to indexer | Call without target $@. | Quality.cs:32:9:32:21 | access to indexer | access to indexer |
| Quality.cs:38:16:38:26 | access to property MyProperty2 | Call without target $@. | Quality.cs:38:16:38:26 | access to property MyProperty2 | access to property MyProperty2 |
| Quality.cs:50:20:50:26 | object creation of type T | Call without target $@. | Quality.cs:50:20:50:26 | object creation of type T | object creation of type T |

View File

@@ -29,7 +29,7 @@ public class Test
var slice = sp[..3]; // TODO: this is not an indexer call, but rather a `sp.Slice(0, 3)` call.
Span<byte> guidBytes = stackalloc byte[16];
guidBytes[08] = 1;
guidBytes[08] = 1; // TODO: this indexer call has no target, because the target is a `ref` returning getter.
new MyList([new(), new Test()]);
}

View File

@@ -1,17 +0,0 @@
/**
* Checks that every live (non-dead) annotation in the test function's
* own scope is reachable from the function entry in the CFG.
* Annotations in nested scopes (generators, async, lambdas, comprehensions)
* have separate CFGs and are excluded from this check.
*/
import OldCfgImpl
private module Utils = EvalOrderCfgUtils<OldCfg>;
private import Utils
private import Utils::CfgTests
from TimerCfgNode a, TestFunction f
where allLiveReachable(a, f)
select a, "Unreachable live annotation; entry of $@ does not reach this node", f, f.getName()

View File

@@ -1,14 +0,0 @@
/**
* Checks that every timer annotation has a corresponding CFG node.
*/
import OldCfgImpl
private module Utils = EvalOrderCfgUtils<OldCfg>;
private import Utils::CfgTests
from TimerAnnotation ann
where annotationWithoutCfgNode(ann)
select ann, "Annotation in $@ has no CFG node", ann.getTestFunction(),
ann.getTestFunction().getName()

View File

@@ -1,21 +0,0 @@
/**
* Checks that within a basic block, if a node is annotated then its
* successor is also annotated (or excluded). A gap in annotations
* within a basic block indicates a missing annotation, since there
* are no branches to justify the gap.
*
* Nodes with exceptional successors are excluded, as the exception
* edge leaves the basic block and the normal successor may be dead.
*/
import OldCfgImpl
private module Utils = EvalOrderCfgUtils<OldCfg>;
private import Utils
private import Utils::CfgTests
from TimerCfgNode a, CfgNode succ
where basicBlockAnnotationGap(a, succ)
select a, "Annotated node followed by unannotated $@ in the same basic block", succ,
succ.getNode().toString()

View File

@@ -1,14 +0,0 @@
| test_boolean.py:9:10:9:43 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:9:59:9:59 | IntegerLiteral | timestamp 2 | test_boolean.py:9:19:9:19 | IntegerLiteral | timestamp 0 |
| test_boolean.py:15:10:15:43 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:15:50:15:50 | IntegerLiteral | timestamp 1 | test_boolean.py:15:20:15:20 | IntegerLiteral | timestamp 0 |
| test_boolean.py:21:10:21:42 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:21:49:21:49 | IntegerLiteral | timestamp 1 | test_boolean.py:21:19:21:19 | IntegerLiteral | timestamp 0 |
| test_boolean.py:27:10:27:34 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:27:50:27:50 | IntegerLiteral | timestamp 2 | test_boolean.py:27:20:27:20 | IntegerLiteral | timestamp 0 |
| test_boolean.py:40:10:40:61 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:40:86:40:86 | IntegerLiteral | timestamp 3 | test_boolean.py:40:16:40:16 | IntegerLiteral | timestamp 0 |
| test_boolean.py:46:10:46:61 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:46:86:46:86 | IntegerLiteral | timestamp 3 | test_boolean.py:46:16:46:16 | IntegerLiteral | timestamp 0 |
| test_boolean.py:52:10:52:95 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:52:120:52:120 | IntegerLiteral | timestamp 4 | test_boolean.py:52:20:52:20 | IntegerLiteral | timestamp 0 |
| test_boolean.py:52:10:52:95 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:52:120:52:120 | IntegerLiteral | timestamp 4 | test_boolean.py:52:63:52:63 | IntegerLiteral | timestamp 2 |
| test_boolean.py:52:11:52:47 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:52:63:52:63 | IntegerLiteral | timestamp 2 | test_boolean.py:52:20:52:20 | IntegerLiteral | timestamp 0 |
| test_boolean.py:64:10:64:52 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:64:59:64:59 | IntegerLiteral | timestamp 6 | test_boolean.py:64:17:64:17 | IntegerLiteral | timestamp 0 |
| test_boolean.py:64:10:64:52 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:64:59:64:59 | IntegerLiteral | timestamp 6 | test_boolean.py:64:27:64:27 | IntegerLiteral | timestamp 2 |
| test_boolean.py:76:10:76:51 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:76:58:76:58 | IntegerLiteral | timestamp 6 | test_boolean.py:76:17:76:17 | IntegerLiteral | timestamp 0 |
| test_boolean.py:76:10:76:51 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:76:58:76:58 | IntegerLiteral | timestamp 6 | test_boolean.py:76:27:76:27 | IntegerLiteral | timestamp 2 |
| test_if.py:96:9:96:29 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_if.py:96:36:96:36 | IntegerLiteral | timestamp 4 | test_if.py:96:15:96:15 | IntegerLiteral | timestamp 2 |

View File

@@ -1,16 +0,0 @@
/**
* Checks that within a single basic block, annotations appear in
* increasing minimum-timestamp order.
*/
import OldCfgImpl
private module Utils = EvalOrderCfgUtils<OldCfg>;
private import Utils
private import Utils::CfgTests
from TimerCfgNode a, TimerCfgNode b, int minA, int minB
where basicBlockOrdering(a, b, minA, minB)
select a, "Basic block ordering: $@ appears before $@", a.getTimestampExpr(minA),
"timestamp " + minA, b.getTimestampExpr(minB), "timestamp " + minB

View File

@@ -1,12 +0,0 @@
| test_boolean.py:9:26:9:27 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 2) | test_boolean.py:9:33:9:33 | IntegerLiteral | Timestamp 1 | test_boolean.py:7:1:7:27 | Function test_and_both_sides | test_and_both_sides |
| test_boolean.py:15:10:15:14 | False | $@ in $@ has no consecutive successor (expected 1) | test_boolean.py:15:20:15:20 | IntegerLiteral | Timestamp 0 | test_boolean.py:13:1:13:30 | Function test_and_short_circuit | test_and_short_circuit |
| test_boolean.py:21:10:21:13 | True | $@ in $@ has no consecutive successor (expected 1) | test_boolean.py:21:19:21:19 | IntegerLiteral | Timestamp 0 | test_boolean.py:19:1:19:29 | Function test_or_short_circuit | test_or_short_circuit |
| test_boolean.py:27:26:27:27 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 2) | test_boolean.py:27:33:27:33 | IntegerLiteral | Timestamp 1 | test_boolean.py:25:1:25:26 | Function test_or_both_sides | test_or_both_sides |
| test_boolean.py:40:45:40:45 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 3) | test_boolean.py:40:51:40:51 | IntegerLiteral | Timestamp 2 | test_boolean.py:38:1:38:24 | Function test_chained_and | test_chained_and |
| test_boolean.py:46:44:46:45 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 3) | test_boolean.py:46:51:46:51 | IntegerLiteral | Timestamp 2 | test_boolean.py:44:1:44:23 | Function test_chained_or | test_chained_or |
| test_boolean.py:52:11:52:47 | BoolExpr | $@ in $@ has no consecutive successor (expected 3) | test_boolean.py:52:63:52:63 | IntegerLiteral | Timestamp 2 | test_boolean.py:50:1:50:25 | Function test_mixed_and_or | test_mixed_and_or |
| test_boolean.py:52:27:52:31 | False | $@ in $@ has no consecutive successor (expected 2) | test_boolean.py:52:37:52:37 | IntegerLiteral | Timestamp 1 | test_boolean.py:50:1:50:25 | Function test_mixed_and_or | test_mixed_and_or |
| test_boolean.py:52:78:52:79 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 4) | test_boolean.py:52:85:52:85 | IntegerLiteral | Timestamp 3 | test_boolean.py:50:1:50:25 | Function test_mixed_and_or | test_mixed_and_or |
| test_if.py:95:9:95:13 | False | $@ in $@ has no consecutive successor (expected 2) | test_if.py:95:19:95:19 | IntegerLiteral | Timestamp 1 | test_if.py:93:1:93:34 | Function test_if_compound_condition | test_if_compound_condition |
| test_if.py:96:9:96:29 | BoolExpr | $@ in $@ has no consecutive successor (expected 5) | test_if.py:96:36:96:36 | IntegerLiteral | Timestamp 4 | test_if.py:93:1:93:34 | Function test_if_compound_condition | test_if_compound_condition |
| test_if.py:96:22:96:22 | y | $@ in $@ has no consecutive successor (expected 4) | test_if.py:96:28:96:28 | IntegerLiteral | Timestamp 3 | test_if.py:93:1:93:34 | Function test_if_compound_condition | test_if_compound_condition |

View File

@@ -1,24 +0,0 @@
/**
* Checks that consecutive annotated nodes have consecutive timestamps:
* for each annotation with timestamp `a`, some CFG node for that annotation
* must have a next annotation containing `a + 1`.
*
* Handles CFG splitting (e.g., finally blocks duplicated for normal/exceptional
* flow) by checking that at least one split has the required successor.
*
* Only applies to functions where all annotations are in the function's
* own scope (excludes tests with generators, async, comprehensions, or
* lambdas that have annotations in nested scopes).
*/
import OldCfgImpl
private module Utils = EvalOrderCfgUtils<OldCfg>;
private import Utils
private import Utils::CfgTests
from TimerAnnotation ann, int a
where consecutiveTimestamps(ann, a)
select ann, "$@ in $@ has no consecutive successor (expected " + (a + 1) + ")",
ann.getTimestampExpr(a), "Timestamp " + a, ann.getTestFunction(), ann.getTestFunction().getName()

View File

@@ -1,17 +0,0 @@
/**
* Checks that timestamps form a contiguous sequence {0, 1, ..., max}
* within each test function. Every integer in the range must appear
* in at least one annotation (live or dead).
*/
import TimerUtils
from TestFunction f, int missing, int maxTs, TimerAnnotation maxAnn
where
maxTs = max(TimerAnnotation a | a.getTestFunction() = f | a.getATimestamp()) and
maxAnn.getTestFunction() = f and
maxAnn.getATimestamp() = maxTs and
missing = [0 .. maxTs] and
not exists(TimerAnnotation a | a.getTestFunction() = f and a.getATimestamp() = missing)
select f, "Missing timestamp " + missing + " (max is $@)", maxAnn.getTimestampExpr(maxTs),
maxTs.toString()

View File

@@ -1,15 +0,0 @@
/**
* Finds expressions in test functions that lack a timer annotation
* and are not part of the timer mechanism or otherwise excluded.
* An empty result means every annotatable expression is covered.
*/
import python
import TimerUtils
from TestFunction f, Expr e
where
e.getScope().getEnclosingScope*() = f and
not isTimerMechanism(e, f) and
not isUnannotatable(e)
select e, "Missing annotation in $@", f, f.getName()

View File

@@ -1,2 +0,0 @@
| test_match.py:159:13:159:13 | IntegerLiteral | Node annotated with t.never is reachable in $@ | test_match.py:151:1:151:42 | Function test_match_exhaustive_return_first | test_match_exhaustive_return_first |
| test_match.py:172:13:172:13 | IntegerLiteral | Node annotated with t.never is reachable in $@ | test_match.py:164:1:164:45 | Function test_match_exhaustive_return_wildcard | test_match_exhaustive_return_wildcard |

View File

@@ -1,16 +0,0 @@
/**
* Checks that expressions annotated with `t.never` either have no CFG
* node, or if they do, that the node is not reachable from its scope's
* entry (including within the same basic block).
*/
import OldCfgImpl
private module Utils = EvalOrderCfgUtils<OldCfg>;
private import Utils::CfgTests
from TimerAnnotation ann
where neverReachable(ann)
select ann, "Node annotated with t.never is reachable in $@", ann.getTestFunction(),
ann.getTestFunction().getName()

View File

@@ -1,11 +0,0 @@
| test_boolean.py:9:10:9:43 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:9:59:9:59 | IntegerLiteral | 2 | test_boolean.py:9:10:9:13 | ControlFlowNode for True | True | test_boolean.py:9:19:9:19 | IntegerLiteral | 0 |
| test_boolean.py:15:10:15:43 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:15:50:15:50 | IntegerLiteral | 1 | test_boolean.py:15:10:15:14 | ControlFlowNode for False | False | test_boolean.py:15:20:15:20 | IntegerLiteral | 0 |
| test_boolean.py:21:10:21:42 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:21:49:21:49 | IntegerLiteral | 1 | test_boolean.py:21:10:21:13 | ControlFlowNode for True | True | test_boolean.py:21:19:21:19 | IntegerLiteral | 0 |
| test_boolean.py:27:10:27:34 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:27:50:27:50 | IntegerLiteral | 2 | test_boolean.py:27:10:27:14 | ControlFlowNode for False | False | test_boolean.py:27:20:27:20 | IntegerLiteral | 0 |
| test_boolean.py:40:10:40:61 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:40:86:40:86 | IntegerLiteral | 3 | test_boolean.py:40:10:40:10 | ControlFlowNode for IntegerLiteral | IntegerLiteral | test_boolean.py:40:16:40:16 | IntegerLiteral | 0 |
| test_boolean.py:46:10:46:61 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:46:86:46:86 | IntegerLiteral | 3 | test_boolean.py:46:10:46:10 | ControlFlowNode for IntegerLiteral | IntegerLiteral | test_boolean.py:46:16:46:16 | IntegerLiteral | 0 |
| test_boolean.py:52:10:52:95 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:52:120:52:120 | IntegerLiteral | 4 | test_boolean.py:52:11:52:47 | ControlFlowNode for BoolExpr | BoolExpr | test_boolean.py:52:63:52:63 | IntegerLiteral | 2 |
| test_boolean.py:52:11:52:47 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:52:63:52:63 | IntegerLiteral | 2 | test_boolean.py:52:11:52:14 | ControlFlowNode for True | True | test_boolean.py:52:20:52:20 | IntegerLiteral | 0 |
| test_boolean.py:64:10:64:52 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:64:59:64:59 | IntegerLiteral | 6 | test_boolean.py:64:11:64:11 | ControlFlowNode for f | f | test_boolean.py:64:17:64:17 | IntegerLiteral | 0 |
| test_boolean.py:76:10:76:51 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:76:58:76:58 | IntegerLiteral | 6 | test_boolean.py:76:11:76:11 | ControlFlowNode for f | f | test_boolean.py:76:17:76:17 | IntegerLiteral | 0 |
| test_if.py:96:9:96:29 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_if.py:96:36:96:36 | IntegerLiteral | 4 | test_if.py:96:9:96:9 | ControlFlowNode for x | x | test_if.py:96:15:96:15 | IntegerLiteral | 2 |

View File

@@ -1,17 +0,0 @@
/**
* Checks that time never flows backward between consecutive timer annotations
* in the CFG. For each pair of consecutive annotated nodes (A -> B), there must
* exist timestamps a in A and b in B with a < b.
*/
import OldCfgImpl
private module Utils = EvalOrderCfgUtils<OldCfg>;
private import Utils
private import Utils::CfgTests
from TimerCfgNode a, TimerCfgNode b, int minA, int maxB
where noBackwardFlow(a, b, minA, maxB)
select a, "Backward flow: $@ flows to $@ (max timestamp $@)", a.getTimestampExpr(minA),
minA.toString(), b, b.getNode().toString(), b.getTimestampExpr(maxB), maxB.toString()

View File

@@ -1,14 +0,0 @@
/**
* Checks that every annotated CFG node belongs to a basic block.
*/
import OldCfgImpl
private module Utils = EvalOrderCfgUtils<OldCfg>;
private import Utils
private import Utils::CfgTests
from CfgNode n, TestFunction f
where noBasicBlock(n, f)
select n, "CFG node in $@ does not belong to any basic block", f, f.getName()

View File

@@ -1,16 +0,0 @@
/**
* Checks that two annotations sharing a timestamp value are on
* mutually exclusive CFG paths (neither can reach the other).
*/
import OldCfgImpl
private module Utils = EvalOrderCfgUtils<OldCfg>;
private import Utils
private import Utils::CfgTests
from TimerCfgNode a, TimerCfgNode b, int ts
where noSharedReachable(a, b, ts)
select a, "Shared timestamp $@ but this node reaches $@", a.getTimestampExpr(ts), ts.toString(), b,
b.getNode().toString()

View File

@@ -1,16 +0,0 @@
/**
* Implementation of the evaluation-order CFG signature using the existing
* Python control flow graph.
*/
private import python as PY
import TimerUtils
/** Existing Python CFG implementation of the evaluation-order signature. */
module OldCfg implements EvalOrderCfgSig {
class CfgNode = PY::ControlFlowNode;
class BasicBlock = PY::BasicBlock;
CfgNode scopeGetEntryNode(PY::Scope s) { result = s.getEntryNode() }
}

View File

@@ -1,11 +0,0 @@
| test_boolean.py:9:10:9:43 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:9:59:9:59 | IntegerLiteral | timestamp 2 | test_boolean.py:9:19:9:19 | IntegerLiteral | timestamp 0 |
| test_boolean.py:15:10:15:43 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:15:50:15:50 | IntegerLiteral | timestamp 1 | test_boolean.py:15:20:15:20 | IntegerLiteral | timestamp 0 |
| test_boolean.py:21:10:21:42 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:21:49:21:49 | IntegerLiteral | timestamp 1 | test_boolean.py:21:19:21:19 | IntegerLiteral | timestamp 0 |
| test_boolean.py:27:10:27:34 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:27:50:27:50 | IntegerLiteral | timestamp 2 | test_boolean.py:27:20:27:20 | IntegerLiteral | timestamp 0 |
| test_boolean.py:40:10:40:61 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:40:86:40:86 | IntegerLiteral | timestamp 3 | test_boolean.py:40:16:40:16 | IntegerLiteral | timestamp 0 |
| test_boolean.py:46:10:46:61 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:46:86:46:86 | IntegerLiteral | timestamp 3 | test_boolean.py:46:16:46:16 | IntegerLiteral | timestamp 0 |
| test_boolean.py:52:10:52:95 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:52:120:52:120 | IntegerLiteral | timestamp 4 | test_boolean.py:52:63:52:63 | IntegerLiteral | timestamp 2 |
| test_boolean.py:52:11:52:47 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:52:63:52:63 | IntegerLiteral | timestamp 2 | test_boolean.py:52:20:52:20 | IntegerLiteral | timestamp 0 |
| test_boolean.py:64:10:64:52 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:64:59:64:59 | IntegerLiteral | timestamp 6 | test_boolean.py:64:17:64:17 | IntegerLiteral | timestamp 0 |
| test_boolean.py:76:10:76:51 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:76:58:76:58 | IntegerLiteral | timestamp 6 | test_boolean.py:76:17:76:17 | IntegerLiteral | timestamp 0 |
| test_if.py:96:9:96:29 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_if.py:96:36:96:36 | IntegerLiteral | timestamp 4 | test_if.py:96:15:96:15 | IntegerLiteral | timestamp 2 |

View File

@@ -1,17 +0,0 @@
/**
* Stronger version of NoBackwardFlow: for consecutive annotated nodes
* A -> B that both have a single timestamp (non-loop code) and B does
* NOT dominate A (forward edge), requires max(A) < min(B).
*/
import OldCfgImpl
private module Utils = EvalOrderCfgUtils<OldCfg>;
private import Utils
private import Utils::CfgTests
from TimerCfgNode a, TimerCfgNode b, int maxA, int minB
where strictForward(a, b, maxA, minB)
select a, "Strict forward violation: $@ flows to $@", a.getTimestampExpr(maxA), "timestamp " + maxA,
b.getTimestampExpr(minB), "timestamp " + minB

View File

@@ -1,614 +0,0 @@
/**
* Utility library for identifying timer annotations in evaluation-order tests.
*
* Identifies `expr @ t[n]` (matmul) and `t(expr, n)` (call) patterns,
* including `dead(n)` and `never` markers within subscripts, extracts
* timestamp values, and provides predicates for traversing consecutive
* annotated CFG nodes.
*/
import python
/**
* A function decorated with `@test` from the timer module.
* The first parameter is the timer object.
*/
class TestFunction extends Function {
TestFunction() {
this.getADecorator().(Name).getId() = "test" and
this.getPositionalParameterCount() >= 1
}
/** Gets the name of the timer parameter (first parameter). */
string getTimerParamName() { result = this.getArgName(0) }
}
/**
* Gets an element from a timestamp subscript index. Each element is either
* an `IntegerLiteral` (live), a `Call` to `dead` (dead), a `Name("never")`
* (never), or a tuple containing any mix of these.
*/
private Expr timestampElement(Expr timestamps) {
result = timestamps and not timestamps instanceof Tuple
or
result = timestamps.(Tuple).getAnElt()
}
/** Gets a live timestamp value from a subscript index expression. */
private IntegerLiteral liveTimestampLiteral(Expr timestamps) {
result = timestampElement(timestamps) and
not result = any(Call c).getAnArg()
}
/** Gets a dead timestamp value from a subscript index expression. */
private IntegerLiteral deadTimestampLiteral(Expr timestamps) {
exists(Call c |
c = timestampElement(timestamps) and
c.getFunc().(Name).getId() = "dead" and
result = c.getArg(0)
)
}
/** Holds if the subscript index contains `never`. */
private predicate hasNever(Expr timestamps) {
timestampElement(timestamps).(Name).getId() = "never"
}
/** A timer annotation in the AST. */
private newtype TTimerAnnotation =
/** `expr @ t[n]` or `expr @ t[n, m, ...]` or `expr @ t[dead(n), m, never]` */
TMatmulAnnotation(TestFunction func, Expr annotated, Expr timestamps) {
exists(BinaryExpr be |
be.getOp() instanceof MatMult and
be.getRight().(Subscript).getObject().(Name).getId() = func.getTimerParamName() and
be.getScope().getEnclosingScope*() = func and
annotated = be.getLeft() and
timestamps = be.getRight().(Subscript).getIndex()
)
} or
/** `t(expr, n)` */
TCallAnnotation(TestFunction func, Expr annotated, Expr timestamps) {
exists(Call call |
call.getFunc().(Name).getId() = func.getTimerParamName() and
call.getScope().getEnclosingScope*() = func and
annotated = call.getArg(0) and
timestamps = call.getArg(1)
)
}
/** A timer annotation (wrapping the newtype for a clean API). */
class TimerAnnotation extends TTimerAnnotation {
/** Gets a live timestamp value from this annotation. */
int getATimestamp() { exists(this.getTimestampExpr(result)) }
/** Gets the source expression for live timestamp value `ts`. */
IntegerLiteral getTimestampExpr(int ts) {
result = liveTimestampLiteral(this.getTimestampsExpr()) and
result.getValue() = ts
}
/** Gets a dead timestamp value from this annotation. */
int getADeadTimestamp() { exists(this.getDeadTimestampExpr(result)) }
/** Gets the source expression for dead timestamp value `ts`. */
IntegerLiteral getDeadTimestampExpr(int ts) {
result = deadTimestampLiteral(this.getTimestampsExpr()) and
result.getValue() = ts
}
/** Gets the raw timestamp expression (single element or tuple). */
abstract Expr getTimestampsExpr();
/** Gets the test function this annotation belongs to. */
abstract TestFunction getTestFunction();
/** Gets the annotated expression (the LHS of `@` or the first arg of `t(...)`). */
abstract Expr getAnnotatedExpr();
/** Gets the enclosing annotation expression (the `BinaryExpr` or `Call`). */
abstract Expr getTimerExpr();
/** Holds if timestamp `ts` is marked as dead in this annotation. */
predicate isDeadTimestamp(int ts) { ts = this.getADeadTimestamp() }
/** Holds if all timestamps in this annotation are dead (no live timestamps). */
predicate isDead() {
not exists(this.getATimestamp()) and
not this.isNever() and
exists(this.getADeadTimestamp())
}
/** Holds if this is a never-evaluated annotation (contains `never`). */
predicate isNever() { hasNever(this.getTimestampsExpr()) }
string toString() { result = this.getAnnotatedExpr().toString() }
Location getLocation() { result = this.getAnnotatedExpr().getLocation() }
}
/** A matmul-based timer annotation: `expr @ t[...]`. */
class MatmulTimerAnnotation extends TMatmulAnnotation, TimerAnnotation {
TestFunction func;
Expr annotated;
Expr timestamps;
MatmulTimerAnnotation() { this = TMatmulAnnotation(func, annotated, timestamps) }
override Expr getTimestampsExpr() { result = timestamps }
override TestFunction getTestFunction() { result = func }
override Expr getAnnotatedExpr() { result = annotated }
override BinaryExpr getTimerExpr() { result.getLeft() = annotated }
}
/** A call-based timer annotation: `t(expr, n)`. */
class CallTimerAnnotation extends TCallAnnotation, TimerAnnotation {
TestFunction func;
Expr annotated;
Expr timestamps;
CallTimerAnnotation() { this = TCallAnnotation(func, annotated, timestamps) }
override Expr getTimestampsExpr() { result = timestamps }
override TestFunction getTestFunction() { result = func }
override Expr getAnnotatedExpr() { result = annotated }
override Call getTimerExpr() { result.getArg(0) = annotated }
}
/**
* Signature module defining the CFG interface needed by evaluation-order tests.
* This allows the test utilities to be instantiated with different CFG implementations.
*/
signature module EvalOrderCfgSig {
/** A control flow node. */
class CfgNode {
/** Gets a textual representation of this node. */
string toString();
/** Gets the location of this node. */
Location getLocation();
/** Gets the AST node corresponding to this CFG node, if any. */
AstNode getNode();
/** Gets a successor of this CFG node (including exceptional). */
CfgNode getASuccessor();
/** Gets a true-branch successor of this CFG node, if any. */
CfgNode getATrueSuccessor();
/** Gets a false-branch successor of this CFG node, if any. */
CfgNode getAFalseSuccessor();
/** Gets an exceptional successor of this CFG node. */
CfgNode getAnExceptionalSuccessor();
/** Gets the scope containing this CFG node. */
Scope getScope();
/** Gets the basic block containing this CFG node. */
BasicBlock getBasicBlock();
}
/** A basic block in the control flow graph. */
class BasicBlock {
/** Gets the CFG node at position `n` in this basic block. */
CfgNode getNode(int n);
/** Holds if this basic block reaches `bb` (reflexive). */
predicate reaches(BasicBlock bb);
/** Holds if this basic block strictly reaches `bb` (non-reflexive). */
predicate strictlyReaches(BasicBlock bb);
/** Holds if this basic block strictly dominates `bb`. */
predicate strictlyDominates(BasicBlock bb);
}
/** Gets the entry CFG node for scope `s`. */
CfgNode scopeGetEntryNode(Scope s);
}
/**
* Parameterised module providing CFG-dependent utilities for evaluation-order tests.
* Instantiate with a specific CFG implementation to get `TimerCfgNode` and related predicates.
*/
module EvalOrderCfgUtils<EvalOrderCfgSig Input> {
/** The CFG node type from the underlying implementation. */
final class CfgNode = Input::CfgNode;
/** The basic block type from the underlying implementation (named to avoid clash with `python::BasicBlock`). */
final class CfgBasicBlock = Input::BasicBlock;
/** Gets the entry CFG node for scope `s`. */
CfgNode scopeGetEntryNode(Scope s) { result = Input::scopeGetEntryNode(s) }
/**
* A CFG node corresponding to a timer annotation.
*/
class TimerCfgNode extends CfgNode {
private TimerAnnotation annot;
TimerCfgNode() { annot.getAnnotatedExpr() = this.getNode() }
/** Gets a timestamp value from this annotation. */
int getATimestamp() { result = annot.getATimestamp() }
/** Gets the source expression for timestamp value `ts`. */
IntegerLiteral getTimestampExpr(int ts) { result = annot.getTimestampExpr(ts) }
/** Gets the test function this annotation belongs to. */
TestFunction getTestFunction() { result = annot.getTestFunction() }
/** Holds if timestamp `ts` is marked as dead. */
predicate isDeadTimestamp(int ts) { annot.isDeadTimestamp(ts) }
/** Holds if all timestamps in this annotation are dead. */
predicate isDead() { annot.isDead() }
/** Holds if this is a never-evaluated annotation. */
predicate isNever() { annot.isNever() }
}
/**
* Holds if `next` is the next timer annotation reachable from `n` via
* CFG successors (both normal and exceptional), skipping non-annotated
* intermediaries within the same scope.
*/
predicate nextTimerAnnotation(CfgNode n, TimerCfgNode next) {
next = n.getASuccessor() and
next.getScope() = n.getScope()
or
exists(CfgNode mid |
mid = n.getASuccessor() and
not mid instanceof TimerCfgNode and
mid.getScope() = n.getScope() and
nextTimerAnnotation(mid, next)
)
}
/**
* Holds if `next` is the next timer annotation reachable from `n` via
* the true branch, skipping non-annotated intermediaries and after-value
* nodes for the same AST node.
*/
predicate nextTimerAnnotationFromTrue(CfgNode n, TimerCfgNode next) {
exists(CfgNode trueSucc |
trueSucc = n.getATrueSuccessor() and
trueSucc.getScope() = n.getScope()
|
// If the true successor is a different annotated node, use it
next = trueSucc and next.getNode() != n.getNode()
or
// Otherwise skip through it (it's an after-value node for the same expr)
nextTimerAnnotation(trueSucc, next)
)
}
/**
* Holds if `next` is the next timer annotation reachable from `n` via
* the false branch, skipping non-annotated intermediaries and after-value
* nodes for the same AST node.
*/
predicate nextTimerAnnotationFromFalse(CfgNode n, TimerCfgNode next) {
exists(CfgNode falseSucc |
falseSucc = n.getAFalseSuccessor() and
falseSucc.getScope() = n.getScope()
|
// If the false successor is a different annotated node, use it
next = falseSucc and next.getNode() != n.getNode()
or
// Otherwise skip through it (it's an after-value node for the same expr)
nextTimerAnnotation(falseSucc, next)
)
}
/** CFG-dependent test predicates, one per evaluation-order query. */
module CfgTests {
/**
* Holds if live annotation `a` in function `f` is unreachable from
* the function entry in the CFG.
*/
predicate allLiveReachable(TimerCfgNode a, TestFunction f) {
not a.isDead() and
f = a.getTestFunction() and
a.getScope() = f and
not scopeGetEntryNode(f).getBasicBlock().reaches(a.getBasicBlock())
}
/**
* Holds if annotated node `a` is followed by unannotated `succ` in the
* same basic block.
*/
predicate basicBlockAnnotationGap(TimerCfgNode a, CfgNode succ) {
exists(CfgBasicBlock bb, int i |
a = bb.getNode(i) and
succ = bb.getNode(i + 1)
) and
not succ instanceof TimerCfgNode and
not isUnannotatable(succ.getNode()) and
not isTimerMechanism(succ.getNode(), a.getTestFunction()) and
not exists(a.getAnExceptionalSuccessor()) and
succ.getNode() instanceof Expr
}
/**
* Holds if annotations `a` and `b` appear in the same basic block with
* `a` before `b`, but `a`'s minimum timestamp is not less than `b`'s.
*/
predicate basicBlockOrdering(TimerCfgNode a, TimerCfgNode b, int minA, int minB) {
exists(CfgBasicBlock bb, int i, int j | a = bb.getNode(i) and b = bb.getNode(j) and i < j) and
minA = min(a.getATimestamp()) and
minB = min(b.getATimestamp()) and
minA >= minB
}
/**
* Holds if function `f` has an annotation in a nested scope
* (generator, async function, comprehension, lambda).
*/
private predicate hasNestedScopeAnnotation(TestFunction f) {
exists(TimerAnnotation a |
a.getTestFunction() = f and
a.getAnnotatedExpr().getScope() != f
)
}
/**
* Holds if annotation `ann` with timestamp `a` has no consecutive
* successor (expected `a + 1`) in the CFG.
*/
predicate consecutiveTimestamps(TimerAnnotation ann, int a) {
not hasNestedScopeAnnotation(ann.getTestFunction()) and
not ann.isDead() and
a = ann.getATimestamp() and
not exists(TimerCfgNode x, TimerCfgNode y |
ann.getAnnotatedExpr() = x.getNode() and
nextTimerAnnotation(x, y) and
(a + 1) = y.getATimestamp()
) and
// Exclude the maximum timestamp in the function (it has no successor)
not a =
max(TimerAnnotation other |
other.getTestFunction() = ann.getTestFunction()
|
other.getATimestamp()
)
}
/**
* Holds if the expression annotated with `t.never` is reachable from
* its scope's entry.
*/
predicate neverReachable(TimerAnnotation ann) {
ann.isNever() and
exists(CfgNode n, Scope s |
n.getNode() = ann.getAnnotatedExpr() and
s = n.getScope() and
(
// Reachable via inter-block path (includes same block)
scopeGetEntryNode(s).getBasicBlock().reaches(n.getBasicBlock())
or
// In same block as entry but at a later index
exists(CfgBasicBlock bb, int i, int j |
bb.getNode(i) = scopeGetEntryNode(s) and bb.getNode(j) = n and i < j
)
)
)
}
/**
* Holds if consecutive annotated nodes `a` -> `b` have backward time
* flow (`minA >= maxB`).
*/
predicate noBackwardFlow(TimerCfgNode a, TimerCfgNode b, int minA, int maxB) {
nextTimerAnnotation(a, b) and
not a.isDead() and
not b.isDead() and
minA = min(a.getATimestamp()) and
maxB = max(b.getATimestamp()) and
minA >= maxB
}
/**
* Holds if annotations `a` and `b` share timestamp `ts` but `a`
* can reach `b` in the CFG.
*/
predicate noSharedReachable(TimerCfgNode a, TimerCfgNode b, int ts) {
a != b and
not a.isDead() and
not b.isDead() and
a.getTestFunction() = b.getTestFunction() and
ts = a.getATimestamp() and
ts = b.getATimestamp() and
(
a.getBasicBlock().strictlyReaches(b.getBasicBlock())
or
exists(CfgBasicBlock bb, int i, int j | a = bb.getNode(i) and b = bb.getNode(j) and i < j)
)
}
/**
* Holds if consecutive single-timestamp annotations `a` -> `b` on a
* forward edge have `maxA >= minB`.
*/
predicate strictForward(TimerCfgNode a, TimerCfgNode b, int maxA, int minB) {
nextTimerAnnotation(a, b) and
not a.isDead() and
not b.isDead() and
// Only apply to non-loop code (single timestamps on both sides)
strictcount(a.getATimestamp()) = 1 and
strictcount(b.getATimestamp()) = 1 and
// Forward edge: B does not strictly dominate A (excludes loop back-edges
// but still checks same-basic-block pairs)
not b.getBasicBlock().strictlyDominates(a.getBasicBlock()) and
maxA = max(a.getATimestamp()) and
minB = min(b.getATimestamp()) and
maxA >= minB
}
/**
* Holds if CFG node `n` in test function `f` does not belong to any basic block.
*/
predicate noBasicBlock(CfgNode n, TestFunction f) {
n.getScope() = f and
not exists(n.getBasicBlock())
}
/**
* Holds if non-dead annotation `ann` has no corresponding CFG node.
*/
predicate annotationWithoutCfgNode(TimerAnnotation ann) {
not ann.isDead() and
not ann.isNever() and
not exists(CfgNode n | n.getNode() = ann.getAnnotatedExpr())
}
predicate annotationWithCfgNode(TimerAnnotation ann) {
exists(CfgNode n | n.getNode() = ann.getAnnotatedExpr())
}
/**
* Holds if annotation `ann` with timestamp `a` has no consecutive
* predecessor (expected `a - 1`) in the CFG.
*/
predicate consecutivePredecessorTimestamps(TimerAnnotation ann, int a) {
not hasNestedScopeAnnotation(ann.getTestFunction()) and
not ann.isDead() and
a = ann.getATimestamp() and
not exists(TimerCfgNode x, TimerCfgNode y |
ann.getAnnotatedExpr() = y.getNode() and
nextTimerAnnotation(x, y) and
(a - 1) = x.getATimestamp()
) and
// Exclude the minimum timestamp in the function (it has no predecessor)
not a =
min(TimerAnnotation other |
other.getTestFunction() = ann.getTestFunction() and
not other.isDead()
|
other.getATimestamp()
)
}
/**
* Holds if `node` has both a true and false successor, but the true
* successor's timestamp `ts` is not marked as dead on the false
* successor (or vice versa).
*
* This checks that boolean branches are properly annotated: when a
* condition splits into true/false paths, the next annotated node
* on each side should account for the other side's timestamps as dead.
*/
predicate missingBranchTimestamp(TimerCfgNode node, int ts, string branch) {
not hasNestedScopeAnnotation(node.getTestFunction()) and
exists(TimerCfgNode trueNext, TimerCfgNode falseNext |
nextTimerAnnotationFromTrue(node, trueNext) and
nextTimerAnnotationFromFalse(node, falseNext) and
trueNext != falseNext
|
// True successor has live timestamp ts, but false successor
// doesn't have it as dead
ts = trueNext.getATimestamp() and
not falseNext.isDeadTimestamp(ts) and
not ts = falseNext.getATimestamp() and
branch = "false"
or
// False successor has live timestamp ts, but true successor
// doesn't have it as dead
ts = falseNext.getATimestamp() and
not trueNext.isDeadTimestamp(ts) and
not ts = trueNext.getATimestamp() and
branch = "true"
)
}
}
}
/**
* Holds if `e` is part of the timer mechanism: a top-level timer
* expression or a (transitive) sub-expression of one.
*/
predicate isTimerMechanism(Expr e, TestFunction f) {
exists(TimerAnnotation a |
a.getTestFunction() = f and
e = a.getTimerExpr().getASubExpression*()
)
}
/**
* Holds if expression `e` cannot be annotated due to Python syntax
* limitations (e.g., it is a definition target, a pattern, or part
* of a decorator application).
*/
predicate isUnannotatable(Expr e) {
// Function/class definitions
e instanceof FunctionExpr
or
e instanceof ClassExpr
or
// Docstrings are string literals used as expression statements
e instanceof StringLiteral and e.getParent() instanceof ExprStmt
or
// Function parameters are bound by the call, not evaluated in the body
e instanceof Parameter
or
// Name nodes that are definitions or deletions (assignment targets, def/class
// name bindings, augmented assignment targets, for-loop targets, del targets)
e.(Name).isDefinition()
or
e.(Name).isDeletion()
or
// Tuple/List/Starred nodes in assignment or for-loop targets are
// structural unpack patterns, not evaluations
(e instanceof Tuple or e instanceof List or e instanceof Starred) and
e = any(AssignStmt a).getATarget().getASubExpression*()
or
(e instanceof Tuple or e instanceof List or e instanceof Starred) and
e = any(For f).getTarget().getASubExpression*()
or
// The decorator call node wrapping a function/class definition,
// and its sub-expressions (the decorator name itself)
e = any(FunctionExpr func).getADecoratorCall().getASubExpression*()
or
e = any(ClassExpr cls).getADecoratorCall().getASubExpression*()
or
// Augmented assignment (x += e): the implicit BinaryExpr for the operation
e = any(AugAssign aug).getOperation()
or
// with-statement `as` variables are bindings
(e instanceof Name or e instanceof Tuple or e instanceof List) and
e = any(With w).getOptionalVars().getASubExpression*()
or
// except-clause exception type and `as` variable are part of except syntax
exists(ExceptStmt ex | e = ex.getType() or e = ex.getName())
or
// match/case pattern expressions are part of pattern syntax
e.getParent+() instanceof Pattern
or
// Subscript/Attribute nodes on the LHS of an assignment are store
// operations, not value expressions (including nested ones like d["a"][1])
(e instanceof Subscript or e instanceof Attribute) and
e = any(AssignStmt a).getATarget().getASubExpression*()
or
// Match/case guard nodes are part of case syntax
e instanceof Guard
or
// Yield/YieldFrom in statement position — the return value is
// discarded and cannot be meaningfully annotated
(e instanceof Yield or e instanceof YieldFrom) and
e.getParent() instanceof ExprStmt
or
// Synthetic nodes inside desugared comprehensions
e.getScope() = any(Comp c).getFunction() and
(
e.(Name).getId() = ".0"
or
e instanceof Tuple and e.getParent() instanceof Yield
)
}

View File

@@ -1,56 +0,0 @@
"""Assert and raise statement evaluation order."""
from timer import test, dead
@test
def test_assert_true(t):
x = True @ t[0]
assert x @ t[1]
y = 1 @ t[2]
@test
def test_assert_true_with_message(t):
x = True @ t[0]
assert x @ t[1], "msg" @ t[dead(2)]
y = 1 @ t[2]
@test
def test_assert_false_caught(t):
try:
x = False @ t[0]
assert x @ t[1], "fail" @ t[2]
except AssertionError:
y = 1 @ t[3]
@test
def test_raise_caught(t):
try:
x = 1 @ t[0]
raise ((ValueError @ t[1])("test" @ t[2]) @ t[3])
except ValueError:
y = 2 @ t[4]
@test
def test_raise_from_caught(t):
try:
x = 1 @ t[0]
raise ((ValueError @ t[1])("test" @ t[2]) @ t[3]) from ((RuntimeError @ t[4])("cause" @ t[5]) @ t[6])
except ValueError:
y = 2 @ t[7]
@test
def test_bare_reraise(t):
try:
try:
raise ((ValueError @ t[0])("test" @ t[1]) @ t[2])
except ValueError:
x = 1 @ t[3]
raise
except ValueError:
y = 2 @ t[4]

View File

@@ -1,97 +0,0 @@
"""Async/await evaluation order tests.
Coroutine bodies are lazy — like generators, the body runs only when
awaited (or driven by the event loop). asyncio.run() drives the
coroutine to completion synchronously from the caller's perspective.
"""
import asyncio
from contextlib import asynccontextmanager
from timer import test
@test
def test_simple_async(t):
"""Simple async function: body runs inside asyncio.run()."""
async def coro():
x = 1 @ t[4]
return x @ t[5]
result = ((asyncio @ t[0]).run @ t[1])((coro @ t[2])() @ t[3]) @ t[6]
@test
def test_await_expression(t):
"""await suspends the caller until the inner coroutine completes."""
async def helper():
return 1 @ t[4]
async def main():
x = await helper() @ t[5]
return x @ t[6]
result = ((asyncio @ t[0]).run @ t[1])((main @ t[2])() @ t[3]) @ t[7]
@test
def test_async_for(t):
"""async for iterates an async generator."""
async def agen():
yield 1 @ t[5]
yield 2 @ t[7]
async def main():
async for val in agen() @ t[4]:
val @ t[6, 8]
((asyncio @ t[0]).run @ t[1])((main @ t[2])() @ t[3]) @ t[9]
@test
def test_async_with(t):
"""async with enters/exits an async context manager."""
@asynccontextmanager
async def ctx():
yield 1 @ t[5]
async def main():
async with ctx() @ t[4] as val:
val @ t[6]
((asyncio @ t[0]).run @ t[1])((main @ t[2])() @ t[3]) @ t[7]
@test
def test_multiple_awaits(t):
"""Sequential awaits in one coroutine."""
async def task_a():
return 10 @ t[4]
async def task_b():
return 20 @ t[6]
async def main():
a = await task_a() @ t[5]
b = await task_b() @ t[7]
return (a @ t[8] + b @ t[9]) @ t[10]
result = ((asyncio @ t[0]).run @ t[1])((main @ t[2])() @ t[3]) @ t[11]
@test
def test_gather(t):
"""asyncio.gather schedules coroutines as concurrent tasks."""
async def task_a():
return 1 @ t[6]
async def task_b():
return 2 @ t[7]
async def main():
results = await asyncio.gather(
task_a() @ t[4],
task_b() @ t[5],
) @ t[8]
return results @ t[9]
result = ((asyncio @ t[0]).run @ t[1])((main @ t[2])() @ t[3]) @ t[10]

View File

@@ -1,53 +0,0 @@
"""Augmented assignment evaluation order."""
from timer import test
@test
def test_plus_equals(t):
x = 1 @ t[0]
x += 2 @ t[1]
y = x @ t[2]
@test
def test_sub_mul_div(t):
x = 20 @ t[0]
x -= 5 @ t[1]
x *= 2 @ t[2]
x /= 6 @ t[3]
x = 17 @ t[4]
x //= 3 @ t[5]
x %= 3 @ t[6]
y = x @ t[7]
@test
def test_power_equals(t):
x = 2 @ t[0]
x **= 3 @ t[1]
y = x @ t[2]
@test
def test_bitwise_equals(t):
x = 0b1111 @ t[0]
x &= 0b1010 @ t[1]
x |= 0b0101 @ t[2]
x ^= 0b0011 @ t[3]
y = x @ t[4]
@test
def test_shift_equals(t):
x = 1 @ t[0]
x <<= 4 @ t[1]
x >>= 2 @ t[2]
y = x @ t[3]
@test
def test_list_extend(t):
x = [1 @ t[0], 2 @ t[1]] @ t[2]
x += [3 @ t[3], 4 @ t[4]] @ t[5]
y = x @ t[6]

View File

@@ -1,223 +0,0 @@
"""Basic expression evaluation order.
These tests verify that sub-expressions within a single expression
are evaluated in the expected order (typically left to right for
operands of binary operators, elements of collection literals, etc.)
Every evaluated expression has a timestamp annotation, except the
timer mechanism itself (t[n], t[dead(n)], t[never]).
"""
from timer import test, never
@test
def test_sequential_statements(t):
"""Statements execute top to bottom."""
x = 1 @ t[0]
y = 2 @ t[1]
z = 3 @ t[2]
@test
def test_binary_add(t):
"""In a + b, left operand evaluates before right."""
x = (1 @ t[0] + 2 @ t[1]) @ t[2]
@test
def test_binary_subtract(t):
"""In a - b, left operand evaluates before right."""
x = (10 @ t[0] - 3 @ t[1]) @ t[2]
@test
def test_binary_multiply(t):
"""In a * b, left operand evaluates before right."""
x = ((3 @ t[0]) * (4 @ t[1])) @ t[2]
@test
def test_nested_binary(t):
"""Sub-expressions evaluate before their containing expression."""
x = ((1 @ t[0] + 2 @ t[1]) @ t[2] + (3 @ t[3] + 4 @ t[4]) @ t[5]) @ t[6]
@test
def test_chained_add(t):
"""a + b + c is (a + b) + c: left to right."""
x = (1 @ t[0] + 2 @ t[1] + 3 @ t[2]) @ t[3]
@test
def test_mixed_precedence(t):
"""In a + b * c, all operands still evaluate left to right."""
x = (1 @ t[0] + ((2 @ t[1]) * (3 @ t[2])) @ t[3]) @ t[4]
@test
def test_string_concat(t):
"""String concatenation operands: left to right."""
x = ("hello" @ t[0] + " " @ t[1] + "world" @ t[2]) @ t[3]
@test
def test_comparison(t):
"""In a < b, left operand evaluates before right."""
x = (1 @ t[0] < 2 @ t[1]) @ t[2]
@test
def test_chained_comparison(t):
"""Chained a < b < c: all evaluated left to right (b only once)."""
x = (1 @ t[0] < 2 @ t[1] < 3 @ t[2]) @ t[3]
@test
def test_list_elements(t):
"""List elements evaluate left to right."""
x = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3]
@test
def test_dict_entries(t):
"""Dict: key before value, entries left to right."""
d = {1 @ t[0]: "a" @ t[1], 2 @ t[2]: "b" @ t[3]} @ t[4]
@test
def test_tuple_elements(t):
"""Tuple elements evaluate left to right."""
x = (1 @ t[0], 2 @ t[1], 3 @ t[2]) @ t[3]
@test
def test_set_elements(t):
"""Set elements evaluate left to right."""
x = {1 @ t[0], 2 @ t[1], 3 @ t[2]} @ t[3]
@test
def test_subscript(t):
"""In obj[idx], object evaluates before index."""
x = ([10 @ t[0], 20 @ t[1], 30 @ t[2]] @ t[3])[1 @ t[4]] @ t[5]
@test
def test_slice(t):
"""Slice parameters: object, then start, then stop."""
x = ([1 @ t[0], 2 @ t[1], 3 @ t[2], 4 @ t[3], 5 @ t[4]] @ t[5])[1 @ t[6]:3 @ t[7]] @ t[8]
@test
def test_method_call(t):
"""Object evaluated, then attribute lookup, then arguments left to right, then call."""
x = (("hello world" @ t[0]).replace @ t[1])("world" @ t[2], "there" @ t[3]) @ t[4]
@test
def test_method_chaining(t):
"""Chained method calls: left to right."""
x = ((((" hello " @ t[0]).strip @ t[1])() @ t[2]).upper @ t[3])() @ t[4]
@test
def test_unary_not(t):
"""Unary not: operand evaluated first."""
x = (not True @ t[0]) @ t[1]
@test
def test_unary_neg(t):
"""Unary negation: operand evaluated first."""
x = (-(3 @ t[0])) @ t[1]
@test
def test_multiple_assignment(t):
"""RHS evaluated once in x = y = expr."""
x = y = (1 @ t[0] + 2 @ t[1]) @ t[2]
@test
def test_callable_syntax(t):
"""t(value, n) is equivalent to value @ t[n]."""
x = t(t(1, 0) + t(2, 1), 2)
y = t(t(x, 3) * t(3, 4), 5)
@test
def test_subscript_assign(t):
"""In obj[idx] = val, value is evaluated before target sub-expressions."""
lst = [0 @ t[0], 0 @ t[1], 0 @ t[2]] @ t[3]
(lst @ t[5])[1 @ t[6]] = 42 @ t[4]
x = lst @ t[7]
@test
def test_attribute_assign(t):
"""In obj.attr = val, value is evaluated before the object."""
class Obj:
pass
o = (Obj @ t[0])() @ t[1]
(o @ t[3]).x = 42 @ t[2]
y = (o @ t[4]).x @ t[5]
@test
def test_nested_subscript_assign(t):
"""Nested subscript assignment: val, then outer obj, then keys."""
d = {"a" @ t[0]: [0 @ t[1], 0 @ t[2]] @ t[3]} @ t[4]
(d @ t[6])["a" @ t[7]][1 @ t[8]] = 99 @ t[5]
x = d @ t[9]
@test
def test_unreachable_after_return(t):
"""Code after return has no CFG node."""
def f():
x = 1 @ t[1]
return x @ t[2]
y = 2 @ t[never]
result = (f @ t[0])() @ t[3]
@test
def test_none_literal(t):
"""None is a name constant."""
x = None @ t[0]
y = (x @ t[1] is None @ t[2]) @ t[3]
@test
def test_delete(t):
"""del statement removes a variable binding."""
x = 1 @ t[0]
del x
y = 2 @ t[1]
@test
def test_global(t):
"""global statement allows writing to module-level variable."""
global _test_global_var
_test_global_var = 1 @ t[0]
x = _test_global_var @ t[1]
@test
def test_nonlocal(t):
"""nonlocal statement allows inner function to rebind outer variable."""
x = 0 @ t[0]
def inner():
nonlocal x
x = 1 @ t[2]
(inner @ t[1])() @ t[3]
y = x @ t[4]
@test
def test_walrus(t):
"""Walrus operator := evaluates the RHS and binds it."""
if (y := 1 @ t[0]) @ t[1]:
z = y @ t[2]

View File

@@ -1,76 +0,0 @@
"""Short-circuit boolean operators and evaluation order."""
from timer import test, dead
@test
def test_and_both_sides(t):
# True and X — both operands evaluated, result is X
x = (True @ t[0] and 42 @ t[1, dead(2)]) @ t[dead(1), 2]
@test
def test_and_short_circuit(t):
# False and ... — right side never evaluated
x = (False @ t[0] and True @ t[dead(1)]) @ t[1, dead(2)]
@test
def test_or_short_circuit(t):
# True or ... — right side never evaluated
x = (True @ t[0] or False @ t[dead(1)]) @ t[1, dead(2)]
@test
def test_or_both_sides(t):
# False or X — both operands evaluated, result is X
x = (False @ t[0] or 42 @ t[1]) @ t[dead(1), 2]
@test
def test_not(t):
# not evaluates its operand, then negates
x = (not True @ t[0]) @ t[1]
y = (not False @ t[2]) @ t[3]
@test
def test_chained_and(t):
# 1 and 2 and 3 — all truthy, all evaluated left-to-right
x = (1 @ t[0] and 2 @ t[1, dead(3)] and 3 @ t[2, dead(3)]) @ t[dead(1), dead(2), 3]
@test
def test_chained_or(t):
# 0 or "" or 42 — first two falsy, all evaluated until truthy found
x = (0 @ t[0] or "" @ t[1, dead(3)] or 42 @ t[2, dead(3)]) @ t[dead(1), dead(2), 3]
@test
def test_mixed_and_or(t):
# True and False or 42 => (True and False) or 42 => False or 42 => 42
x = ((True @ t[0] and False @ t[1, dead(2)]) @ t[dead(1), 2, dead(4)] or 42 @ t[3, dead(4)]) @ t[dead(2), dead(3), 4]
@test
def test_and_side_effects(t):
# Both functions called when left side is truthy
def f():
return 10 @ t[1]
def g():
return 20 @ t[4]
x = ((f @ t[0])() @ t[2] and (g @ t[3])() @ t[5]) @ t[6]
@test
def test_or_side_effects(t):
# Both functions called when left side is falsy
def f():
return 0 @ t[1]
def g():
return 20 @ t[4]
x = ((f @ t[0])() @ t[2] or (g @ t[3])() @ t[5]) @ t[6]

View File

@@ -1,74 +0,0 @@
"""Class definitions — evaluation order."""
from timer import test
@test
def test_simple_class(t):
"""Simple class definition and instantiation."""
class Foo:
pass
obj = (Foo @ t[0])() @ t[1]
@test
def test_class_with_bases(t):
"""Base class expressions evaluated at class definition time."""
class Base:
pass
class Derived(Base @ t[0]):
pass
obj = (Derived @ t[1])() @ t[2]
@test
def test_class_with_methods(t):
"""Object evaluated before method is called."""
class Foo:
def greet(self, name):
return ("hello " @ t[5] + name @ t[6]) @ t[7]
obj = (Foo @ t[0])() @ t[1]
msg = ((obj @ t[2]).greet @ t[3])("world" @ t[4]) @ t[8]
@test
def test_class_instantiation(t):
"""Arguments to __init__ evaluate before instantiation completes."""
class Foo:
def __init__(self, x):
(self @ t[3]).x = x @ t[2]
obj = (Foo @ t[0])(42 @ t[1]) @ t[4]
val = (obj @ t[5]).x @ t[6]
@test
def test_method_call(t):
"""Method arguments evaluate left-to-right before the call."""
class Calculator:
def __init__(self, value):
(self @ t[3]).value = value @ t[2]
def add(self, x):
return ((self @ t[8]).value @ t[9] + x @ t[10]) @ t[11]
calc = (Calculator @ t[0])(10 @ t[1]) @ t[4]
result = ((calc @ t[5]).add @ t[6])(5 @ t[7]) @ t[12]
@test
def test_class_level_attribute(t):
"""Multiple attribute accesses in a single expression."""
class Config:
debug = True @ t[0]
version = 1 @ t[1]
x = ((Config @ t[2]).debug @ t[3], (Config @ t[4]).version @ t[5]) @ t[6]
@test
def test_class_decorator(t):
"""Decorator expression evaluated, class defined, then decorator called."""
def add_marker(cls):
(cls @ t[2]).marked = True @ t[1]
return cls @ t[3]
@(add_marker @ t[0])
class Foo:
pass
result = (Foo @ t[4]).marked @ t[5]

View File

@@ -1,46 +0,0 @@
"""Evaluation order tests for comprehensions and generator expressions."""
from timer import test
@test
def test_list_comprehension(t):
items = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3]
result = [x @ t[5, 6, 7] for x in items @ t[4]] @ t[8]
@test
def test_filtered_comprehension(t):
items = [1 @ t[0], 2 @ t[1], 3 @ t[2], 4 @ t[3]] @ t[4]
result = [x @ t[14, 23] for x in items @ t[5] if (x @ t[6, 10, 15, 19] % 2 @ t[7, 11, 16, 20] == 0 @ t[8, 12, 17, 21]) @ t[9, 13, 18, 22]] @ t[24]
@test
def test_dict_comprehension(t):
items = [("a" @ t[0], 1 @ t[1]) @ t[2], ("b" @ t[3], 2 @ t[4]) @ t[5]] @ t[6]
result = {k @ t[8, 10]: v @ t[9, 11] for k, v in items @ t[7]} @ t[12]
@test
def test_set_comprehension(t):
items = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3]
result = {x @ t[5, 6, 7] for x in items @ t[4]} @ t[8]
@test
def test_generator_expression(t):
items = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3]
gen = (x @ t[8, 9, 10] for x in items @ t[4]) @ t[5]
result = (list @ t[6])(gen @ t[7]) @ t[11]
@test
def test_nested_comprehension(t):
matrix = [[1 @ t[0], 2 @ t[1]] @ t[2], [3 @ t[3], 4 @ t[4]] @ t[5]] @ t[6]
result = [x @ t[9, 10, 12, 13] for row in matrix @ t[7] for x in row @ t[8, 11]] @ t[14]
@test
def test_comprehension_with_call(t):
items = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3]
result = [(str @ t[5, 8, 11])(x @ t[6, 9, 12]) @ t[7, 10, 13] for x in items @ t[4]] @ t[14]

View File

@@ -1,44 +0,0 @@
"""Ternary conditional expressions and evaluation order."""
from timer import test, dead
@test
def test_ternary_true(t):
# Condition is True — consequent evaluated, alternative skipped
x = (1 @ t[1] if True @ t[0] else 2 @ t[dead(1)]) @ t[2]
@test
def test_ternary_false(t):
# Condition is False — alternative evaluated, consequent skipped
x = (1 @ t[dead(1)] if False @ t[0] else 2 @ t[1]) @ t[2]
@test
def test_ternary_nested(t):
# Nested: outer condition True, inner condition True
# ((10 if C1 else 20) if C2 else 30) — C2 first, then C1, then 10
x = ((10 @ t[2] if True @ t[1] else 20 @ t[dead(2)]) @ t[3] if True @ t[0] else 30 @ t[dead(1)]) @ t[4]
@test
def test_ternary_assignment(t):
# Ternary result assigned, then used in later expression
value = (100 @ t[1] if True @ t[0] else 200 @ t[dead(1)]) @ t[2]
result = (value @ t[3] + 1 @ t[4]) @ t[5]
@test
def test_ternary_complex_expressions(t):
# Complex sub-expressions in condition and consequent
x = ((1 @ t[3] + 2 @ t[4]) @ t[5] if (3 @ t[0] > 2 @ t[1]) @ t[2] else (4 @ t[dead(3)] + 5 @ t[dead(4)]) @ t[dead(5)]) @ t[6]
@test
def test_ternary_as_argument(t):
# Ternary used as a function argument
def f(a):
return a @ t[4]
result = (f @ t[0])((1 @ t[2] if True @ t[1] else 2 @ t[dead(2)]) @ t[3]) @ t[5]

View File

@@ -1,34 +0,0 @@
"""F-string evaluation order."""
from timer import test
@test
def test_simple_fstring(t):
name = "world" @ t[0]
s = f"hello {name @ t[1]}" @ t[2]
@test
def test_multi_expr_fstring(t):
a = "hello" @ t[0]
b = "world" @ t[1]
s = f"{a @ t[2]} {b @ t[3]}" @ t[4]
@test
def test_nested_fstring(t):
inner = "world" @ t[0]
s = f"hello {f'dear {inner @ t[1]}' @ t[2]}" @ t[3]
@test
def test_format_spec(t):
x = 3.14159 @ t[0]
s = f"{x @ t[1]:.2f}" @ t[2]
@test
def test_method_in_fstring(t):
name = "world" @ t[0]
s = f"hello {((name @ t[1]).upper @ t[2])() @ t[3]}" @ t[4]

View File

@@ -1,85 +0,0 @@
"""Function calls and definitions — evaluation order."""
from timer import test
@test
def test_argument_order(t):
"""Arguments evaluate left-to-right before the call."""
def add(a, b):
return (a @ t[3] + b @ t[4]) @ t[5]
result = (add @ t[0])(1 @ t[1], 2 @ t[2]) @ t[6]
@test
def test_multiple_arguments(t):
"""All arguments left-to-right, then the call."""
def f(a, b, c):
return ((a @ t[4] + b @ t[5]) @ t[6] + c @ t[7]) @ t[8]
result = (f @ t[0])(1 @ t[1], 2 @ t[2], 3 @ t[3]) @ t[9]
@test
def test_default_arguments(t):
"""Default expressions are evaluated at definition time."""
val = 5 @ t[0]
def f(a, b=val @ t[1]):
return (a @ t[4] + b @ t[5]) @ t[6]
result = (f @ t[2])(10 @ t[3]) @ t[7]
@test
def test_args_kwargs(t):
"""*args and **kwargs — expressions evaluated before the call."""
def f(*args, **kwargs):
return ((sum @ t[9])(args @ t[10]) @ t[11] + (sum @ t[12])(((kwargs @ t[13]).values @ t[14])() @ t[15]) @ t[16]) @ t[17]
args = [1 @ t[0], 2 @ t[1]] @ t[2]
kwargs = {"c" @ t[3]: 3 @ t[4]} @ t[5]
result = (f @ t[6])(*args @ t[7], **kwargs @ t[8]) @ t[18]
@test
def test_nested_calls(t):
"""Inner call completes before becoming an argument to outer call."""
def f(x):
return (x @ t[7] + 1 @ t[8]) @ t[9]
def g(x):
return (x @ t[3] * 2 @ t[4]) @ t[5]
result = (f @ t[0])((g @ t[1])(1 @ t[2]) @ t[6]) @ t[10]
@test
def test_function_as_argument(t):
"""Function object is just another argument, evaluated left-to-right."""
def apply(fn, x):
return (fn @ t[3])(x @ t[4]) @ t[8]
def double(x):
return (x @ t[5] * 2 @ t[6]) @ t[7]
result = (apply @ t[0])(double @ t[1], 5 @ t[2]) @ t[9]
@test
def test_decorator(t):
"""Decorator: expression evaluated, function defined, decorator called."""
def my_decorator(fn):
return fn @ t[1]
@(my_decorator @ t[0])
def f():
return 42 @ t[3]
result = (f @ t[2])() @ t[4]
@test
def test_keyword_arguments(t):
"""Keyword argument values evaluate left-to-right."""
def f(a, b):
return (a @ t[3] + b @ t[4]) @ t[5]
result = (f @ t[0])(a=1 @ t[1], b=2 @ t[2]) @ t[6]
@test
def test_return_value(t):
"""The return value is just the result of the call expression."""
def f(x):
return (x @ t[2] * 2 @ t[3]) @ t[4]
result = (f @ t[0])(3 @ t[1]) @ t[5]

View File

@@ -1,108 +0,0 @@
"""If/elif/else control flow evaluation order."""
from timer import test, dead
@test
def test_if_true(t):
x = True @ t[0]
if x @ t[1]:
y = 1 @ t[2]
z = 0 @ t[3]
@test
def test_if_false(t):
x = False @ t[0]
if x @ t[1]:
y = 1 @ t[dead(2)]
z = 0 @ t[2]
@test
def test_if_else_true(t):
x = True @ t[0]
if x @ t[1]:
y = 1 @ t[2]
else:
y = 2 @ t[dead(2)]
z = 0 @ t[3]
@test
def test_if_else_false(t):
x = False @ t[0]
if x @ t[1]:
y = 1 @ t[dead(2)]
else:
y = 2 @ t[2]
z = 0 @ t[3]
@test
def test_if_elif_else_first(t):
x = 1 @ t[0]
if (x @ t[1] == 1 @ t[2]) @ t[3]:
y = "first" @ t[4]
elif (x @ t[dead(4)] == 2 @ t[dead(5)]) @ t[dead(6)]:
y = "second" @ t[dead(4)]
else:
y = "third" @ t[dead(4)]
z = 0 @ t[5]
@test
def test_if_elif_else_second(t):
x = 2 @ t[0]
if (x @ t[1] == 1 @ t[2]) @ t[3]:
y = "first" @ t[dead(7)]
elif (x @ t[4] == 2 @ t[5]) @ t[6]:
y = "second" @ t[7]
else:
y = "third" @ t[dead(7)]
z = 0 @ t[8]
@test
def test_if_elif_else_third(t):
x = 3 @ t[0]
if (x @ t[1] == 1 @ t[2]) @ t[3]:
y = "first" @ t[dead(7)]
elif (x @ t[4] == 2 @ t[5]) @ t[6]:
y = "second" @ t[dead(7)]
else:
y = "third" @ t[7]
z = 0 @ t[8]
@test
def test_nested_if_else(t):
x = True @ t[0]
y = True @ t[1]
if x @ t[2]:
if y @ t[3]:
z = 1 @ t[4]
else:
z = 2 @ t[dead(4)]
else:
z = 3 @ t[dead(4)]
w = 0 @ t[5]
@test
def test_if_compound_condition(t):
x = True @ t[0]
y = False @ t[1]
if (x @ t[2] and y @ t[3]) @ t[4]:
z = 1 @ t[dead(5)]
else:
z = 2 @ t[5]
w = 0 @ t[6]
@test
def test_if_pass(t):
x = True @ t[0]
if x @ t[1]:
pass
z = 0 @ t[2]

View File

@@ -1,46 +0,0 @@
"""Lambda expressions — evaluation order."""
from timer import test
@test
def test_simple_lambda(t):
"""Lambda creates a function object in one step."""
f = (lambda x: (x @ t[3] + 1 @ t[4]) @ t[5]) @ t[0]
result = (f @ t[1])(10 @ t[2]) @ t[6]
@test
def test_lambda_multiple_args(t):
"""Lambda call: arguments evaluate left to right."""
f = (lambda a, b, c: ((a @ t[5] + b @ t[6]) @ t[7] + c @ t[8]) @ t[9]) @ t[0]
result = (f @ t[1])(1 @ t[2], 2 @ t[3], 3 @ t[4]) @ t[10]
@test
def test_lambda_default(t):
"""Default argument evaluated at lambda creation time."""
val = 5 @ t[0]
f = (lambda x, y=val @ t[1]: (x @ t[5] + y @ t[6]) @ t[7]) @ t[2]
result = (f @ t[3])(10 @ t[4]) @ t[8]
@test
def test_lambda_map(t):
"""Lambda body runs once per element when consumed by list(map(...))."""
f = (lambda x: (x @ t[9, 12, 15] * 2 @ t[10, 13, 16]) @ t[11, 14, 17]) @ t[0]
result = (list @ t[1])((map @ t[2])(f @ t[3], [1 @ t[4], 2 @ t[5], 3 @ t[6]] @ t[7]) @ t[8]) @ t[18]
@test
def test_immediately_invoked(t):
"""Arguments evaluated, then immediately-invoked lambda called."""
result = ((lambda x: (x @ t[2] + 1 @ t[3]) @ t[4]) @ t[0])(10 @ t[1]) @ t[5]
@test
def test_lambda_closure(t):
"""Lambda captures enclosing scope; body runs at call time."""
x = 10 @ t[0]
f = (lambda: x @ t[3]) @ t[1]
result = (f @ t[2])() @ t[4]

View File

@@ -1,146 +0,0 @@
"""Loop control flow evaluation order tests."""
from timer import test, dead
# 1. Simple while loop (fixed iterations)
@test
def test_while_loop(t):
i = 0 @ t[0]
while (i @ t[1, 7, 13, 19] < 3 @ t[2, 8, 14, 20]) @ t[3, 9, 15, 21]: # 4 checks: 3 true + 1 false
i = (i @ t[4, 10, 16] + 1 @ t[5, 11, 17]) @ t[6, 12, 18]
done = True @ t[22]
# 2. While loop with break
@test
def test_while_break(t):
i = 0 @ t[0]
while (i @ t[1, 10, 19] < 5 @ t[2, 11, 20]) @ t[3, 12, 21]:
if (i @ t[4, 13, 22] == 2 @ t[5, 14, 23]) @ t[6, 15, 24]:
break
i = (i @ t[7, 16] + 1 @ t[8, 17]) @ t[9, 18]
done = True @ t[25]
# 3. While loop with continue
@test
def test_while_continue(t):
i = 0 @ t[0]
total = 0 @ t[1]
while (i @ t[2, 14, 23, 35] < 3 @ t[3, 15, 24, 36]) @ t[4, 16, 25, 37]:
i = (i @ t[5, 17, 26] + 1 @ t[6, 18, 27]) @ t[7, 19, 28]
if (i @ t[8, 20, 29] == 2 @ t[9, 21, 30]) @ t[10, 22, 31]:
continue
total = (total @ t[11, 32] + i @ t[12, 33]) @ t[13, 34]
done = True @ t[38]
# 4. While/else (no break — else executes)
@test
def test_while_else(t):
i = 0 @ t[0]
while (i @ t[1, 7, 13] < 2 @ t[2, 8, 14]) @ t[3, 9, 15]:
i = (i @ t[4, 10] + 1 @ t[5, 11]) @ t[6, 12]
else:
done = True @ t[16]
# 5. While/else (with break — else skipped)
@test
def test_while_else_break(t):
i = 0 @ t[0]
while (i @ t[1, 10] < 5 @ t[2, 11]) @ t[3, 12]:
if (i @ t[4, 13] == 1 @ t[5, 14]) @ t[6, 15]:
break
i = (i @ t[7] + 1 @ t[8]) @ t[9]
else:
never = True @ t[dead(16)]
after = True @ t[16]
# 6. Simple for loop over a list
@test
def test_for_list(t):
for x in [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3]:
x @ t[4, 5, 6]
done = True @ t[7]
# 7. For loop with range
@test
def test_for_range(t):
for i in (range @ t[0])(3 @ t[1]) @ t[2]:
i @ t[3, 4, 5]
done = True @ t[6]
# 8. For loop with break
@test
def test_for_break(t):
for x in [1 @ t[0], 2 @ t[1], 3 @ t[2], 4 @ t[3]] @ t[4]:
if (x @ t[5, 9, 13] == 3 @ t[6, 10, 14]) @ t[7, 11, 15]:
break
x @ t[8, 12]
done = True @ t[16]
# 9. For loop with continue
@test
def test_for_continue(t):
total = 0 @ t[0]
for x in [1 @ t[1], 2 @ t[2], 3 @ t[3]] @ t[4]:
if (x @ t[5, 11, 14] == 2 @ t[6, 12, 15]) @ t[7, 13, 16]:
continue
total = (total @ t[8, 17] + x @ t[9, 18]) @ t[10, 19]
done = True @ t[20]
# 10. For/else (no break — else executes)
@test
def test_for_else(t):
for x in [1 @ t[0], 2 @ t[1]] @ t[2]:
x @ t[3, 4]
else:
done = True @ t[5]
# 11. For/else (with break — else skipped)
@test
def test_for_else_break(t):
for x in [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3]:
if (x @ t[4, 8] == 2 @ t[5, 9]) @ t[6, 10]:
break
x @ t[7]
else:
never = True @ t[dead(11)]
after = True @ t[11]
# 12. Nested loops
@test
def test_nested_loops(t):
for i in [1 @ t[0], 2 @ t[1]] @ t[2]:
for j in [10 @ t[3, 12], 20 @ t[4, 13]] @ t[5, 14]:
(i @ t[6, 9, 15, 18, dead(21)] + j @ t[7, 10, 16, 19]) @ t[8, 11, 17, 20]
done = True @ t[dead(3), dead(6), dead(9), dead(12), dead(15), dead(18), 21]
# 13. While True with conditional break
@test
def test_while_true_break(t):
i = 0 @ t[0]
while True @ t[1, 8, 15]:
i = (i @ t[2, 9, 16] + 1 @ t[3, 10, 17]) @ t[4, 11, 18]
if (i @ t[5, 12, 19] == 3 @ t[6, 13, 20]) @ t[7, 14, 21]:
break
done = True @ t[22]
# 14. For with enumerate
@test
def test_for_enumerate(t):
for idx, val in (enumerate @ t[0])(["a" @ t[1], "b" @ t[2], "c" @ t[3]] @ t[4]) @ t[5]:
idx @ t[6, 8, 10]
val @ t[7, 9, 11]
done = True @ t[12]

View File

@@ -1,173 +0,0 @@
"""Evaluation order for match/case (structural pattern matching, Python 3.10+)."""
import sys
if sys.version_info < (3, 10):
print("Skipping match/case tests (requires Python 3.10+)")
print("---")
print("0/0 tests passed")
sys.exit(0)
from timer import test, dead, never
@test
def test_match_literal(t):
x = 1 @ t[0]
match x @ t[1]:
case 1:
y = "one" @ t[2]
case 2:
y = "two" @ t[dead(2)]
z = y @ t[3]
@test
def test_match_literal_fallthrough(t):
x = 3 @ t[0]
match x @ t[1]:
case 1:
y = "one" @ t[dead(2)]
case 2:
y = "two" @ t[dead(2)]
case 3:
y = "three" @ t[2]
z = y @ t[3]
@test
def test_match_wildcard(t):
x = 42 @ t[0]
match x @ t[1]:
case 1:
y = "one" @ t[dead(2)]
case _:
y = "other" @ t[2]
z = y @ t[3]
@test
def test_match_capture(t):
x = 42 @ t[0]
match x @ t[1]:
case n:
y = n @ t[2]
z = y @ t[3]
@test
def test_match_or_pattern(t):
x = 2 @ t[0]
match x @ t[1]:
case 1 | 2:
y = "low" @ t[2]
case _:
y = "other" @ t[dead(2)]
z = y @ t[3]
@test
def test_match_guard(t):
x = 5 @ t[0]
match x @ t[1]:
case n if (n @ t[2] > 3 @ t[3]) @ t[4]:
y = n @ t[5]
case _:
y = 0 @ t[dead(5)]
z = y @ t[6]
@test
def test_match_class_pattern(t):
x = 42 @ t[0]
match x @ t[1]:
case int():
y = "integer" @ t[2]
case str():
y = "string" @ t[dead(2)]
z = y @ t[3]
@test
def test_match_sequence(t):
x = [1 @ t[0], 2 @ t[1]] @ t[2]
match x @ t[3]:
case [a, b]:
y = (a @ t[4] + b @ t[5]) @ t[6]
case _:
y = 0 @ t[dead(6)]
z = y @ t[7]
@test
def test_match_mapping(t):
x = {"key" @ t[0]: 42 @ t[1]} @ t[2]
match x @ t[3]:
case {"key": value}:
y = value @ t[4]
case _:
y = 0 @ t[dead(4)]
z = y @ t[5]
@test
def test_match_nested(t):
x = {"users" @ t[0]: [{"name" @ t[1]: "Alice" @ t[2]} @ t[3]] @ t[4]} @ t[5]
match x @ t[6]:
case {"users": [{"name": name}]}:
y = name @ t[7]
case _:
y = "unknown" @ t[dead(7)]
z = y @ t[8]
@test
def test_match_or_pattern_with_as(t):
"""OR pattern with `as` binding and method call on the result."""
clause = "foo@bar" @ t[0]
match clause @ t[1]:
case (str() as uses) | {"uses": uses}:
result = ((uses @ t[2]).partition @ t[3])("@" @ t[4]) @ t[5]
x = (result @ t[6])[0 @ t[7]] @ t[8]
case _:
raise ((ValueError @ t[dead(2)])(clause @ t[dead(3)]) @ t[dead(4)])
y = x @ t[9]
@test
def test_match_wildcard_raise(t):
"""Wildcard case that raises, with OR pattern on the other branch."""
clause = 42 @ t[0]
try:
match clause @ t[1]:
case (str() as uses) | {"uses": uses}:
result = uses @ t[dead(2)]
case _:
raise ((ValueError @ t[2])(f"Invalid: {clause @ t[3]}" @ t[4]) @ t[5])
except ValueError:
y = 0 @ t[6]
@test
def test_match_exhaustive_return_first(t):
"""Every case returns; code after match is unreachable (first case taken)."""
def f(x):
match x @ t[2]:
case 1:
return "one" @ t[3]
case _:
return "other" @ t[dead(3)]
y = 0 @ t[never]
result = (f @ t[0])(1 @ t[1]) @ t[4]
@test
def test_match_exhaustive_return_wildcard(t):
"""Every case returns; code after match is unreachable (wildcard taken)."""
def f(x):
match x @ t[2]:
case 1:
return "one" @ t[dead(3)]
case _:
return "other" @ t[3]
y = 0 @ t[never]
result = (f @ t[0])(99 @ t[1]) @ t[4]

View File

@@ -1,182 +0,0 @@
"""Exception handling control flow: try/except/else/finally evaluation order."""
from timer import test, dead, never
# 1. try/except — no exception raised (except block skipped)
@test
def test_try_no_exception(t):
try:
x = 1 @ t[0]
y = 2 @ t[1]
except ValueError:
z = 3 @ t[dead(2)]
after = 0 @ t[2]
# 2. try/except — exception raised and caught
@test
def test_try_with_exception(t):
try:
x = 1 @ t[0]
raise ((ValueError @ t[1])() @ t[2])
y = 2 @ t[never]
except ValueError:
z = 3 @ t[3]
after = 0 @ t[4]
# 3. try/except/else — no exception (else runs)
@test
def test_try_except_else_no_exception(t):
try:
x = 1 @ t[0]
except ValueError:
y = 2 @ t[dead(1)]
else:
z = 3 @ t[1]
after = 0 @ t[2]
# 4. try/except/else — exception raised (else skipped)
@test
def test_try_except_else_with_exception(t):
try:
x = 1 @ t[0]
raise ((ValueError @ t[1])() @ t[2])
except ValueError:
y = 2 @ t[3]
else:
z = 3 @ t[dead(3)]
after = 0 @ t[4]
# 5. try/finally — no exception
@test
def test_try_finally_no_exception(t):
try:
x = 1 @ t[0]
y = 2 @ t[1]
finally:
z = 3 @ t[2]
after = 0 @ t[3]
# 6. try/finally — exception raised (finally runs, then exception propagates)
@test
def test_try_finally_exception(t):
try:
try:
x = 1 @ t[0]
raise ((ValueError @ t[1])() @ t[2])
finally:
y = 2 @ t[3]
except ValueError:
z = 3 @ t[4]
# 7. try/except/finally — no exception
@test
def test_try_except_finally_no_exception(t):
try:
x = 1 @ t[0]
except ValueError:
y = 2 @ t[dead(1)]
finally:
z = 3 @ t[1]
after = 0 @ t[2]
# 8. try/except/finally — exception caught
@test
def test_try_except_finally_exception(t):
try:
x = 1 @ t[0]
raise ((ValueError @ t[1])() @ t[2])
except ValueError:
y = 2 @ t[3]
finally:
z = 3 @ t[4]
after = 0 @ t[5]
# 9. Multiple except clauses — first matching
@test
def test_multiple_except_first(t):
try:
x = 1 @ t[0]
raise ((ValueError @ t[1])() @ t[2])
except ValueError:
y = 2 @ t[3]
except TypeError:
z = 3 @ t[dead(3)]
after = 0 @ t[4]
# 10. Multiple except clauses — second matching
@test
def test_multiple_except_second(t):
try:
x = 1 @ t[0]
raise ((TypeError @ t[1])() @ t[2])
except ValueError:
y = 2 @ t[dead(3)]
except TypeError:
z = 3 @ t[3]
after = 0 @ t[4]
# 11. except with `as` binding
@test
def test_except_as_binding(t):
try:
x = 1 @ t[0]
raise ((ValueError @ t[1])("msg" @ t[2]) @ t[3])
except ValueError as e:
y = (str @ t[4])(e @ t[5]) @ t[6]
after = 0 @ t[7]
# 12. Nested try/except
@test
def test_nested_try_except(t):
try:
x = 1 @ t[0]
try:
y = 2 @ t[1]
raise ((ValueError @ t[2])() @ t[3])
except ValueError:
z = 3 @ t[4]
w = 4 @ t[5]
except TypeError:
v = 5 @ t[dead(6)]
after = 0 @ t[6]
# 13. try/except in a loop
@test
def test_try_in_loop(t):
total = 0 @ t[0]
for i in (range @ t[1])(3 @ t[2]) @ t[3]:
try:
if (i @ t[4, 11, 20] == 1 @ t[5, 12, 21]) @ t[6, 13, 22]:
raise ((ValueError @ t[14])() @ t[15])
total = (total @ t[7, 23] + 1 @ t[8, 24]) @ t[9, 25]
except ValueError:
total = (total @ t[16] + 10 @ t[17]) @ t[18]
r = 0 @ t[10, 19, 26]
# 14. Re-raise with bare `raise`
@test
def test_reraise(t):
try:
try:
x = 1 @ t[0]
raise ((ValueError @ t[1])() @ t[2])
except ValueError:
y = 2 @ t[3]
raise
except ValueError:
z = 3 @ t[4]
after = 0 @ t[5]

View File

@@ -1,48 +0,0 @@
"""Unpacking and star expressions evaluation order."""
from timer import test
@test
def test_tuple_unpack(t):
"""RHS expression evaluates, then unpacking assigns targets."""
a, b = (1 @ t[0], 2 @ t[1]) @ t[2]
x = (a @ t[3] + b @ t[4]) @ t[5]
@test
def test_list_unpack(t):
"""List unpacking: RHS elements left to right, then unpack."""
[a, b] = [1 @ t[0], 2 @ t[1]] @ t[2]
x = (a @ t[3] + b @ t[4]) @ t[5]
@test
def test_star_unpack(t):
"""Star unpacking: RHS evaluates first."""
a, *b = [1 @ t[0], 2 @ t[1], 3 @ t[2], 4 @ t[3]] @ t[4]
x = (a @ t[5], b @ t[6]) @ t[7]
@test
def test_nested_unpack(t):
"""Nested unpacking: RHS evaluates first."""
(a, b), c = ((1 @ t[0], 2 @ t[1]) @ t[2], 3 @ t[3]) @ t[4]
x = ((a @ t[5] + b @ t[6]) @ t[7] + c @ t[8]) @ t[9]
@test
def test_swap(t):
a = 1 @ t[0]
b = 2 @ t[1]
a, b = (b @ t[2], a @ t[3]) @ t[4]
x = a @ t[5]
y = b @ t[6]
@test
def test_unpack_for(t):
pairs = [(1 @ t[0], 2 @ t[1]) @ t[2], (3 @ t[3], 4 @ t[4]) @ t[5]] @ t[6]
for a, b in pairs @ t[7]:
x = a @ t[8, 10]
y = b @ t[9, 11]

View File

@@ -1,58 +0,0 @@
"""Evaluation order tests for with statements."""
from contextlib import contextmanager
from timer import test
@contextmanager
def ctx(value=None):
yield value
@test
def test_simple_with(t):
x = 1 @ t[0]
with (ctx @ t[1])() @ t[2]:
y = 2 @ t[3]
z = 3 @ t[4]
@test
def test_with_as(t):
with (ctx @ t[0])(42 @ t[1]) @ t[2] as v:
x = v @ t[3]
y = 0 @ t[4]
@test
def test_nested_with(t):
with (ctx @ t[0])() @ t[1]:
with (ctx @ t[2])() @ t[3]:
x = 1 @ t[4]
y = 2 @ t[5]
@test
def test_multiple_context_managers(t):
with (ctx @ t[0])(1 @ t[1]) @ t[2] as a, (ctx @ t[3])(2 @ t[4]) @ t[5] as b:
x = (a @ t[6], b @ t[7]) @ t[8]
y = 0 @ t[9]
@test
def test_with_exception_handling(t):
try:
with (ctx @ t[0])() @ t[1]:
x = 1 @ t[2]
raise ((ValueError @ t[3])() @ t[4])
except ValueError:
y = 2 @ t[5]
z = 3 @ t[6]
@test
def test_with_in_loop(t):
for i in [1 @ t[0], 2 @ t[1]] @ t[2]:
with (ctx @ t[3, 6])() @ t[4, 7]:
x = i @ t[5, 8]
y = 0 @ t[9]

View File

@@ -1,105 +0,0 @@
"""Generator and yield evaluation order tests.
Generator bodies are lazy — code runs only when iterated. The timer
annotations inside generator bodies fire interleaved with the caller's
annotations, reflecting the suspend/resume semantics of yield.
"""
from timer import test
@test
def test_simple_generator(t):
"""Basic generator: body runs on next(), not on gen()."""
def gen():
yield 1 @ t[4]
yield 2 @ t[8]
g = (gen @ t[0])() @ t[1]
x = (next @ t[2])(g @ t[3]) @ t[5]
y = (next @ t[6])(g @ t[7]) @ t[9]
@test
def test_multiple_yields(t):
"""Three yields interleave with three next() calls."""
def gen():
yield 1 @ t[4]
yield 2 @ t[8]
yield 3 @ t[12]
g = (gen @ t[0])() @ t[1]
a = (next @ t[2])(g @ t[3]) @ t[5]
b = (next @ t[6])(g @ t[7]) @ t[9]
c = (next @ t[10])(g @ t[11]) @ t[13]
@test
def test_generator_for_loop(t):
"""for-loop consumes generator, interleaving body and loop."""
def gen():
yield 1 @ t[2]
yield 2 @ t[4]
for val in (gen @ t[0])() @ t[1]:
val @ t[3, 5]
@test
def test_generator_list(t):
"""list() consumes the entire generator without interleaving."""
def gen():
yield 10 @ t[3]
yield 20 @ t[4]
yield 30 @ t[5]
result = (list @ t[0])((gen @ t[1])() @ t[2]) @ t[6]
@test
def test_yield_from(t):
"""yield from delegates to an inner generator transparently."""
def inner():
yield 1 @ t[6]
yield 2 @ t[10]
def outer():
yield from (inner @ t[4])() @ t[5]
g = (outer @ t[0])() @ t[1]
x = (next @ t[2])(g @ t[3]) @ t[7]
y = (next @ t[8])(g @ t[9]) @ t[11]
@test
def test_generator_return(t):
"""Generator return value accessed via yield from."""
def gen():
yield 1 @ t[6]
return 42 @ t[10]
def wrapper():
result = (yield from (gen @ t[4])() @ t[5]) @ t[11]
yield result @ t[12]
g = (wrapper @ t[0])() @ t[1]
x = (next @ t[2])(g @ t[3]) @ t[7]
y = (next @ t[8])(g @ t[9]) @ t[13]
@test
def test_generator_send(t):
"""send() passes a value into the generator at the yield point."""
def gen():
x = (yield 1 @ t[4]) @ t[9]
yield (x @ t[10] + 10 @ t[11]) @ t[12]
g = (gen @ t[0])() @ t[1]
first = (next @ t[2])(g @ t[3]) @ t[5]
second = ((g @ t[6]).send @ t[7])(42 @ t[8]) @ t[13]
@test
def test_generator_expression(t):
"""Inline generator expression consumed by list()."""
result = (list @ t[0])(x @ t[5, 6, 7] for x in [10 @ t[1], 20 @ t[2], 30 @ t[3]] @ t[4]) @ t[8]

View File

@@ -1,194 +0,0 @@
"""Abstract timer for self-validating CFG evaluation-order tests.
Provides a Timer context manager and a @test decorator for writing tests
that verify the order in which Python evaluates expressions.
Usage with @test decorator (preferred):
from timer import test, dead, never
@test
def test_sequential(t):
x = 1 @ t[0]
y = 2 @ t[1]
z = (x + y) @ t[2]
Annotation forms:
t[n] - assert current timestamp is n, return marker
t[n, m, ...] - assert current timestamp is one of {n, m, ...}
t[dead(n)] - mark timestamp n as dead (fails if evaluated)
t[dead(n), m] - dead at n, live at m
t[never] - mark as never evaluated (fails if evaluated)
t["label"] - record current timestamp under label (development aid)
t(value, n) - equivalent to: value @ t[n]
Run a test file directly to self-validate: python test_file.py
"""
import atexit
import os
import sys
_results = []
class _Check:
"""Marker returned by t[n] — asserts the current timestamp.
Receives the raw subscript elements: plain ints are live timestamps,
dead(n) markers are dead timestamps, and `never` means any evaluation
is an error.
"""
__slots__ = ("_timer", "_live", "_dead", "_never")
def __init__(self, timer, elements):
self._timer = timer
self._live = set()
self._dead = set()
self._never = False
for e in elements:
if isinstance(e, int):
self._live.add(e)
elif isinstance(e, _DeadMarker):
self._dead.add(e.timestamp)
elif isinstance(e, _NeverSentinel):
self._never = True
else:
raise TypeError(
f"Unknown element in timer subscript: {e!r} (type {type(e).__name__})"
)
def __rmatmul__(self, value):
ts = self._timer._tick()
if self._never:
self._timer._error(
f"expression annotated with t[never] was evaluated (timestamp {ts})"
)
elif ts in self._dead:
self._timer._error(
f"timestamp {ts} is marked dead but was evaluated"
)
elif ts not in self._live:
self._timer._error(
f"expected {sorted(self._live)}, got {ts}"
)
return value
class _Label:
"""Marker returned by t["name"] — records the timestamp under a label."""
__slots__ = ("_timer", "_name")
def __init__(self, timer, name):
self._timer = timer
self._name = name
def __rmatmul__(self, value):
ts = self._timer._tick()
self._timer._labels.setdefault(self._name, []).append(ts)
return value
class _DeadMarker:
"""Marker returned by dead(n) — used inside t[...] to mark a timestamp as dead."""
def __init__(self, timestamp):
self.timestamp = timestamp
def dead(n):
"""Mark timestamp `n` as dead code inside a timer subscript: t[dead(1), 2]."""
return _DeadMarker(n)
class _NeverSentinel:
"""Sentinel for never-evaluated annotations: t[never]."""
pass
never = _NeverSentinel()
class Timer:
"""Context manager tracking abstract evaluation timestamps.
Each Timer instance maintains a counter starting at 0. Every time an
annotation (@ t[n] or t(value, n)) is encountered, the counter is
compared against the expected value and then incremented.
"""
def __init__(self, name="<unnamed>"):
self._name = name
self._counter = 0
self._errors = []
self._labels = {}
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self._labels:
for name, timestamps in sorted(self._labels.items()):
print(f" {name}: {', '.join(map(str, timestamps))}")
_results.append((self._name, list(self._errors)))
if self._errors:
print(f"{self._name}: FAIL")
for err in self._errors:
print(f" {err}")
else:
print(f"{self._name}: ok")
return False
def _tick(self):
ts = self._counter
self._counter += 1
return ts
def _error(self, msg):
self._errors.append(msg)
def __getitem__(self, key):
if isinstance(key, str):
return _Label(self, key)
elif isinstance(key, tuple):
return _Check(self, key)
else:
return _Check(self, [key])
def __call__(self, value, key):
"""Alternative to @ operator: t(value, 4) or t(value, [1, 2, 3])."""
if isinstance(key, list):
key = tuple(key)
marker = self[key]
return marker.__rmatmul__(value)
def test(fn):
"""Decorator that creates a Timer and runs the test function immediately.
The function receives a fresh Timer as its sole argument. Errors are
collected (not raised) and reported after the function completes.
"""
with Timer(fn.__name__) as t:
try:
fn(t)
except Exception as e:
t._error(f"exception: {type(e).__name__}: {e}")
return fn
def _report():
"""Print summary at interpreter exit."""
if not _results:
return
total = len(_results)
passed = sum(1 for _, errors in _results if not errors)
print("---")
print(f"{passed}/{total} tests passed")
if passed < total:
os._exit(1)
atexit.register(_report)

View File

@@ -11,7 +11,9 @@ import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as CfgImpl
query predicate nonPostOrderExpr(Expr e, string cls) {
cls = e.getPrimaryQlClasses() and
not exists(e.getDesugared()) and
not e instanceof BodyStmt and
not e instanceof BeginExpr and
not e instanceof Namespace and
not e instanceof Toplevel and
exists(AstNode last, Completion c |
CfgImpl::last(e, last, c) and
last != e and
@@ -25,7 +27,7 @@ query predicate scopeNoFirst(CfgScope scope) {
not scope =
any(Callable c |
not exists(c.getAParameter()) and
not c.getBody().hasEnsure() and
not exists(c.getBody().getARescue())
not c.(BodyStmt).hasEnsure() and
not exists(c.(BodyStmt).getARescue())
)
}

View File

@@ -156,23 +156,14 @@ class ErbDirective extends TDirectiveNode, ErbAstNode {
)
}
pragma[nomagic]
private Stmt getAChildStmt0() {
this.containsAstNodeStart(result) and
not this.containsAstNodeStart(result.getParent())
}
/**
* Gets a statement that starts in directive that is not a child of any other
* statement starting in this directive.
*/
cached
Stmt getAChildStmt() {
result = this.getAChildStmt0() and
not result instanceof BodyStmt
or
this.containsAstNodeStart(result) and
result = this.getAChildStmt0().(BodyStmt).getAStmt()
not this.containsAstNodeStart(result.getParent())
}
/**

View File

@@ -167,8 +167,6 @@ class StmtSequence extends Expr, TStmtSequence {
*/
class BodyStmt extends StmtSequence, TBodyStmt {
final override Stmt getStmt(int n) {
synthChild(this, n, result)
or
toGenerated(result) =
rank[n + 1](Ruby::AstNode node, int i |
node = getBodyStmtChild(this, i) and

View File

@@ -8,7 +8,7 @@ private import internal.TreeSitter
private import internal.Method
/** A callable. */
class Callable extends Expr, Scope, TCallable {
class Callable extends StmtSequence, Expr, Scope, TCallable {
/** Gets the number of parameters of this callable. */
final int getNumberOfParameters() { result = count(this.getAParameter()) }
@@ -18,26 +18,27 @@ class Callable extends Expr, Scope, TCallable {
/** Gets the `n`th parameter of this callable. */
Parameter getParameter(int n) { none() }
/** Gets the body of this callable. */
BodyStmt getBody() { none() }
override AstNode getAChild(string pred) {
result = super.getAChild(pred)
or
pred = "getBody" and result = this.getBody()
or
pred = "getParameter" and result = this.getParameter(_)
}
}
/** A method. */
class MethodBase extends Callable, Scope, TMethodBase {
class MethodBase extends Callable, BodyStmt, Scope, TMethodBase {
/** Gets the name of this method. */
string getName() { none() }
/** Holds if the name of this method is `name`. */
final predicate hasName(string name) { this.getName() = name }
override AstNode getAChild(string pred) {
result = Callable.super.getAChild(pred)
or
result = BodyStmt.super.getAChild(pred)
}
/**
* Holds if this method is public.
* Methods are public by default.
@@ -217,10 +218,6 @@ class Method extends MethodBase, TMethod {
toGenerated(result) = g.getParameters().getChild(n)
}
final override BodyStmt getBody() {
toGenerated(result) = g.getBody() or synthChild(this, _, result)
}
final override string toString() { result = this.getName() }
overlay[global]
@@ -283,10 +280,6 @@ class SingletonMethod extends MethodBase, TSingletonMethod {
toGenerated(result) = g.getParameters().getChild(n)
}
final override BodyStmt getBody() {
toGenerated(result) = g.getBody() or synthChild(this, _, result)
}
final override string toString() { result = this.getName() }
final override AstNode getAChild(string pred) {
@@ -328,7 +321,7 @@ class SingletonMethod extends MethodBase, TSingletonMethod {
* -> (x) { x + 1 }
* ```
*/
class Lambda extends Callable, TLambda {
class Lambda extends Callable, BodyStmt, TLambda {
private Ruby::Lambda g;
Lambda() { this = TLambda(g) }
@@ -339,16 +332,17 @@ class Lambda extends Callable, TLambda {
toGenerated(result) = g.getParameters().getChild(n)
}
final override BodyStmt getBody() {
toGenerated(result) = g.getBody().(Ruby::DoBlock).getBody() or
toGenerated(result) = g.getBody().(Ruby::Block).getBody()
}
final override string toString() { result = "-> { ... }" }
final override AstNode getAChild(string pred) {
result = Callable.super.getAChild(pred)
or
result = BodyStmt.super.getAChild(pred)
}
}
/** A block. */
class Block extends Callable, Scope, TBlock {
class Block extends Callable, StmtSequence, Scope, TBlock {
/**
* Gets a local variable declared by this block.
* For example `local` in `{ | param; local| puts param }`.
@@ -361,15 +355,17 @@ class Block extends Callable, Scope, TBlock {
*/
LocalVariableWriteAccess getLocalVariable(int n) { none() }
final override AstNode getAChild(string pred) {
override AstNode getAChild(string pred) {
result = Callable.super.getAChild(pred)
or
result = StmtSequence.super.getAChild(pred)
or
pred = "getLocalVariable" and result = this.getLocalVariable(_)
}
}
/** A block enclosed within `do` and `end`. */
class DoBlock extends Block, TDoBlock {
class DoBlock extends Block, BodyStmt, TDoBlock {
private Ruby::DoBlock g;
DoBlock() { this = TDoBlock(g) }
@@ -382,10 +378,14 @@ class DoBlock extends Block, TDoBlock {
toGenerated(result) = g.getParameters().getChild(n)
}
final override BodyStmt getBody() { toGenerated(result) = g.getBody() }
final override string toString() { result = "do ... end" }
final override AstNode getAChild(string pred) {
result = Block.super.getAChild(pred)
or
result = BodyStmt.super.getAChild(pred)
}
final override string getAPrimaryQlClass() { result = "DoBlock" }
}

View File

@@ -134,7 +134,7 @@ class BlockParameter extends NamedParameter, TBlockParameter {
final override string getName() { result = g.getName().getValue() }
final override LocalVariable getVariable() {
result.(LocalVariableReal).getDefiningNode() = g.getName() or
result = TLocalVariableReal(_, _, g.getName()) or
result = TLocalVariableSynth(this, 0)
}
@@ -164,7 +164,7 @@ class HashSplatParameter extends NamedParameter, THashSplatParameter {
final override string getAPrimaryQlClass() { result = "HashSplatParameter" }
final override LocalVariable getVariable() {
result.(LocalVariableReal).getDefiningNode() = g.getName() or
result = TLocalVariableReal(_, _, g.getName()) or
result = TLocalVariableSynth(this, 0)
}
@@ -212,9 +212,7 @@ class KeywordParameter extends NamedParameter, TKeywordParameter {
final override string getAPrimaryQlClass() { result = "KeywordParameter" }
final override LocalVariable getVariable() {
result.(LocalVariableReal).getDefiningNode() = g.getName()
}
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
/**
* Gets the default value, i.e. the value assigned to the parameter when one
@@ -264,9 +262,7 @@ class OptionalParameter extends NamedParameter, TOptionalParameter {
*/
final Expr getDefaultValue() { toGenerated(result) = g.getValue() }
final override LocalVariable getVariable() {
result.(LocalVariableReal).getDefiningNode() = g.getName()
}
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
final override string toString() { result = this.getName() }
@@ -297,7 +293,7 @@ class SplatParameter extends NamedParameter, TSplatParameter {
final override string getAPrimaryQlClass() { result = "SplatParameter" }
final override LocalVariable getVariable() {
result.(LocalVariableReal).getDefiningNode() = g.getName() or
result = TLocalVariableReal(_, _, g.getName()) or
result = TLocalVariableSynth(this, 0)
}

View File

@@ -100,16 +100,9 @@ private module Cached {
} or
TBlockArgument(Ruby::BlockArgument g) or
TBlockParameter(Ruby::BlockParameter g) or
TBodyStatement(Ruby::BodyStatement g) {
any(Ruby::Method m).getBody() = g or
any(Ruby::SingletonMethod m).getBody() = g or
any(Ruby::DoBlock b).getBody() = g
} or
TBodyStmtSynth(Ast::AstNode parent, int i) { mkSynthChild(BodyStmtKind(), parent, i) } or
TBooleanLiteralSynth(Ast::AstNode parent, int i, boolean value) {
mkSynthChild(BooleanLiteralKind(value), parent, i)
} or
TBraceBlockBody(Ruby::BlockBody g) or
TBraceBlockSynth(Ast::AstNode parent, int i) { mkSynthChild(BraceBlockKind(), parent, i) } or
TBraceBlockReal(Ruby::Block g) { not g.getParent() instanceof Ruby::Lambda } or
TBreakStmt(Ruby::Break g) or
@@ -207,7 +200,9 @@ private module Cached {
TLambda(Ruby::Lambda g) or
TLine(Ruby::Line g) or
TLeftAssignmentList(Ruby::LeftAssignmentList g) or
TLocalVariableAccessReal(Ruby::Identifier g, TLocalVariableReal v) { access(g, v) } or
TLocalVariableAccessReal(Ruby::Identifier g, TLocalVariableReal v) {
LocalVariableAccess::range(g, v)
} or
TLocalVariableAccessSynth(Ast::AstNode parent, int i, Ast::LocalVariable v) {
mkSynthChild(LocalVariableAccessRealKind(v), parent, i)
or
@@ -367,24 +362,23 @@ private module Cached {
TAssignMulExpr or TAssignRShiftExpr or TAssignSubExpr or TBareStringLiteral or
TBareSymbolLiteral or TBeginBlock or TBeginExpr or TBitwiseAndExprReal or
TBitwiseOrExprReal or TBitwiseXorExprReal or TBlockArgument or TBlockParameter or
TBodyStatement or TBraceBlockBody or TBraceBlockReal or TBreakStmt or TCaseEqExpr or
TCaseExpr or TCaseMatchReal or TCharacterLiteral or TClassDeclaration or
TClassVariableAccessReal or TComplementExpr or TComplexLiteral or TDefinedExprReal or
TDelimitedSymbolLiteral or TDestructuredLeftAssignment or TDestructuredParameter or
TDivExprReal or TDo or TDoBlock or TElementReference or TElseReal or TElsif or TEmptyStmt or
TEncoding or TEndBlock or TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or
TFile or TFindPattern or TFloatLiteral or TForExpr or TForwardParameter or
TForwardArgument or TGEExpr or TGTExpr or TGlobalVariableAccessReal or
THashKeySymbolLiteral or THashLiteral or THashPattern or THashSplatExprReal or
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or
TIfReal or TIfModifierExpr or TInClauseReal or TInstanceVariableAccessReal or
TIntegerLiteralReal or TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or
TLambda or TLeftAssignmentList or TLine or TLocalVariableAccessReal or
TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TMatchPattern or
TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or
TNilLiteralReal or TNoRegExpMatchExpr or TNotExprReal or TOptionalParameter or TPairReal or
TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or TRangeLiteralReal or
TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or
TBraceBlockReal or TBreakStmt or TCaseEqExpr or TCaseExpr or TCaseMatchReal or
TCharacterLiteral or TClassDeclaration or TClassVariableAccessReal or TComplementExpr or
TComplexLiteral or TDefinedExprReal or TDelimitedSymbolLiteral or
TDestructuredLeftAssignment or TDestructuredParameter or TDivExprReal or TDo or TDoBlock or
TElementReference or TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or
TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or TFile or TFindPattern or
TFloatLiteral or TForExpr or TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or
TGlobalVariableAccessReal or THashKeySymbolLiteral or THashLiteral or THashPattern or
THashSplatExprReal or THashSplatNilParameter or THashSplatParameter or THereDoc or
TIdentifierMethodCall or TIfReal or TIfModifierExpr or TInClauseReal or
TInstanceVariableAccessReal or TIntegerLiteralReal or TKeywordParameter or TLEExpr or
TLShiftExprReal or TLTExpr or TLambda or TLeftAssignmentList or TLine or
TLocalVariableAccessReal or TLogicalAndExprReal or TLogicalOrExprReal or TMethod or
TMatchPattern or TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or
TNextStmt or TNilLiteralReal or TNoRegExpMatchExpr or TNotExprReal or TOptionalParameter or
TPairReal or TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or
TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or
TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or TRegularSuperCall or
TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or
TScopeResolutionConstantAccess or TSelfReal or TSimpleParameterReal or
@@ -399,13 +393,13 @@ private module Cached {
class TAstNodeSynth =
TAddExprSynth or TAssignExprSynth or TBitwiseAndExprSynth or TBitwiseOrExprSynth or
TBitwiseXorExprSynth or TBraceBlockSynth or TBodyStmtSynth or TBooleanLiteralSynth or
TCaseMatchSynth or TClassVariableAccessSynth or TConstantReadAccessSynth or
TConstantWriteAccessSynth or TDivExprSynth or TElseSynth or TExponentExprSynth or
TGlobalVariableAccessSynth or TIfSynth or TInClauseSynth or TInstanceVariableAccessSynth or
TIntegerLiteralSynth or TLShiftExprSynth or TLocalVariableAccessSynth or
TLogicalAndExprSynth or TLogicalOrExprSynth or TMethodCallSynth or TModuloExprSynth or
TMulExprSynth or TNilLiteralSynth or TRShiftExprSynth or TRangeLiteralSynth or TSelfSynth or
TBitwiseXorExprSynth or TBraceBlockSynth or TBooleanLiteralSynth or TCaseMatchSynth or
TClassVariableAccessSynth or TConstantReadAccessSynth or TConstantWriteAccessSynth or
TDivExprSynth or TElseSynth or TExponentExprSynth or TGlobalVariableAccessSynth or
TIfSynth or TInClauseSynth or TInstanceVariableAccessSynth or TIntegerLiteralSynth or
TLShiftExprSynth or TLocalVariableAccessSynth or TLogicalAndExprSynth or
TLogicalOrExprSynth or TMethodCallSynth or TModuloExprSynth or TMulExprSynth or
TNilLiteralSynth or TRShiftExprSynth or TRangeLiteralSynth or TSelfSynth or
TSimpleParameterSynth or TSplatExprSynth or THashSplatExprSynth or TStmtSequenceSynth or
TSubExprSynth or TPairSynth or TSimpleSymbolLiteralSynth;
@@ -445,8 +439,6 @@ private module Cached {
n = TBitwiseXorExprReal(result) or
n = TBlockArgument(result) or
n = TBlockParameter(result) or
n = TBodyStatement(result) or
n = TBraceBlockBody(result) or
n = TBraceBlockReal(result) or
n = TBreakStmt(result) or
n = TCaseEqExpr(result) or
@@ -592,8 +584,6 @@ private module Cached {
or
result = TBitwiseXorExprSynth(parent, i)
or
result = TBodyStmtSynth(parent, i)
or
result = TBooleanLiteralSynth(parent, i, _)
or
result = TBraceBlockSynth(parent, i)
@@ -767,9 +757,9 @@ class TElse = TElseReal or TElseSynth;
class TStmtSequence =
TBeginBlock or TEndBlock or TThen or TElse or TDo or TEnsure or TStringInterpolationComponent or
TBodyStmt or TParenthesizedExpr or TStmtSequenceSynth;
TBlock or TBodyStmt or TParenthesizedExpr or TStmtSequenceSynth;
class TBodyStmt = TBeginExpr or TModuleBase or TBraceBlockBody or TBodyStatement or TBodyStmtSynth;
class TBodyStmt = TBeginExpr or TModuleBase or TMethod or TLambda or TDoBlock or TSingletonMethod;
class TNilLiteral = TNilLiteralReal or TNilLiteralSynth;

View File

@@ -14,18 +14,6 @@ class StmtSequenceSynth extends StmtSequence, TStmtSequenceSynth {
final override string toString() { result = "..." }
}
class BodyStatement extends BodyStmt, TBodyStatement {
final override string toString() { result = "..." }
}
class BraceBlockBody extends BodyStmt, TBraceBlockBody {
final override string toString() { result = "..." }
}
class BodyStmtSynth extends BodyStmt, TBodyStmtSynth {
final override string toString() { result = "..." }
}
class Then extends StmtSequence, TThen {
private Ruby::Then g;
@@ -76,9 +64,26 @@ class Ensure extends StmtSequence, TEnsure {
// Not defined by dispatch, as it should not be exposed
Ruby::AstNode getBodyStmtChild(TBodyStmt b, int i) {
result = any(Ruby::BlockBody g | b = TBraceBlockBody(g)).getChild(i)
exists(Ruby::Method g, Ruby::AstNode body | b = TMethod(g) and body = g.getBody() |
result = body.(Ruby::BodyStatement).getChild(i)
or
i = 0 and result = body and not body instanceof Ruby::BodyStatement
)
or
result = any(Ruby::BodyStatement g | b = TBodyStatement(g)).getChild(i)
exists(Ruby::SingletonMethod g, Ruby::AstNode body |
b = TSingletonMethod(g) and body = g.getBody()
|
result = body.(Ruby::BodyStatement).getChild(i)
or
i = 0 and result = body and not body instanceof Ruby::BodyStatement
)
or
exists(Ruby::Lambda g | b = TLambda(g) |
result = g.getBody().(Ruby::DoBlock).getBody().getChild(i) or
result = g.getBody().(Ruby::Block).getBody().getChild(i)
)
or
result = any(Ruby::DoBlock g | b = TDoBlock(g)).getBody().getChild(i)
or
result = any(Ruby::Program g | b = TToplevel(g)).getChild(i) and
not result instanceof Ruby::BeginBlock

View File

@@ -18,7 +18,7 @@ class BraceBlockReal extends BraceBlock, TBraceBlockReal {
toGenerated(result) = g.getParameters().getChild(n)
}
final override BodyStmt getBody() { toGenerated(result) = g.getBody() }
final override Stmt getStmt(int i) { toGenerated(result) = g.getBody().getChild(i) }
}
/**
@@ -28,5 +28,8 @@ class BraceBlockReal extends BraceBlock, TBraceBlockReal {
class BraceBlockSynth extends BraceBlock, TBraceBlockSynth {
final override Parameter getParameter(int n) { synthChild(this, n, result) }
final override BodyStmt getBody() { synthChild(this, _, result) }
final override Stmt getStmt(int i) {
i >= 0 and
synthChild(this, i + this.getNumberOfParameters(), result)
}
}

View File

@@ -33,7 +33,7 @@ class SimpleParameterRealImpl extends SimpleParameterImpl, TSimpleParameterReal
SimpleParameterRealImpl() { this = TSimpleParameterReal(g) }
override LocalVariable getVariableImpl() { result.(LocalVariableReal).getDefiningNode() = g }
override LocalVariable getVariableImpl() { result = TLocalVariableReal(_, _, g) }
override string getNameImpl() { result = g.getValue() }
}

View File

@@ -118,7 +118,7 @@ private Ruby::AstNode specialParentOf(Ruby::AstNode n) {
]
}
Ruby::AstNode parentOf(Ruby::AstNode n) {
private Ruby::AstNode parentOf(Ruby::AstNode n) {
n = getHereDocBody(result)
or
result = specialParentOf(n).getParent()
@@ -172,15 +172,13 @@ private module Cached {
}
}
import Cached
bindingset[n]
pragma[inline_late]
Scope::Range scopeOf(Ruby::AstNode n) { result = Cached::scopeOfImpl(n) }
bindingset[n]
pragma[inline_late]
Scope::Range scopeOf(Ruby::AstNode n) { result = scopeOfImpl(n) }
bindingset[n]
pragma[inline_late]
Scope scopeOfInclSynth(AstNode n) { result = scopeOfInclSynthImpl(n) }
Scope scopeOfInclSynth(AstNode n) { result = Cached::scopeOfInclSynthImpl(n) }
abstract class ScopeImpl extends AstNode, TScopeType {
final Scope getOuterScopeImpl() { result = scopeOfInclSynth(this) }

View File

@@ -19,7 +19,6 @@ newtype TSynthKind =
BitwiseAndExprKind() or
BitwiseOrExprKind() or
BitwiseXorExprKind() or
BodyStmtKind() or
BooleanLiteralKind(boolean value) { value = true or value = false } or
BraceBlockKind() or
CaseMatchKind() or
@@ -74,8 +73,6 @@ class SynthKind extends TSynthKind {
or
this = BitwiseXorExprKind() and result = "BitwiseXorExprKind"
or
this = BodyStmtKind() and result = "BodyStmtKind"
or
this = BooleanLiteralKind(_) and result = "BooleanLiteralKind"
or
this = BraceBlockKind() and result = "BraceBlockKind"
@@ -299,12 +296,9 @@ private predicate hasLocation(AstNode n, Location l) {
private module ImplicitSelfSynthesis {
pragma[nomagic]
private predicate identifierMethodCallSelfSynthesis(AstNode mc, int i, Child child) {
exists(SelfVariableImpl self |
self.getDeclaringScopeImpl() = scopeOf(toGenerated(mc)).getEnclosingSelfScope() and
child = SynthChild(SelfKind(self)) and
mc = TIdentifierMethodCall(_) and
i = 0
)
child = SynthChild(SelfKind(TSelfVariable(scopeOf(toGenerated(mc)).getEnclosingSelfScope()))) and
mc = TIdentifierMethodCall(_) and
i = 0
}
private class IdentifierMethodCallSelfSynthesis extends Synthesis {
@@ -315,14 +309,13 @@ private module ImplicitSelfSynthesis {
pragma[nomagic]
private predicate regularMethodCallSelfSynthesis(TRegularMethodCall mc, int i, Child child) {
exists(Ruby::AstNode g, SelfVariableImpl self |
exists(Ruby::AstNode g |
mc = TRegularMethodCall(g) and
// If there's no explicit receiver, then the receiver is implicitly `self`.
not exists(g.(Ruby::Call).getReceiver()) and
self.getDeclaringScopeImpl() = scopeOf(toGenerated(mc)).getEnclosingSelfScope() and
child = SynthChild(SelfKind(self)) and
i = 0
)
not exists(g.(Ruby::Call).getReceiver())
) and
child = SynthChild(SelfKind(TSelfVariable(scopeOf(toGenerated(mc)).getEnclosingSelfScope()))) and
i = 0
}
private class RegularMethodCallSelfSynthesis extends Synthesis {
@@ -345,10 +338,9 @@ private module ImplicitSelfSynthesis {
*/
pragma[nomagic]
private SelfKind getSelfKind(InstanceVariableAccess var) {
exists(Ruby::AstNode owner, SelfVariableImpl self |
self.getDeclaringScopeImpl() = scopeOf(owner).getEnclosingSelfScope() and
exists(Ruby::AstNode owner |
owner = toGenerated(instanceVarAccessSynthParentStar(var)) and
result = SelfKind(self)
result = SelfKind(TSelfVariable(scopeOf(owner).getEnclosingSelfScope()))
)
}
@@ -1483,24 +1475,17 @@ private module ForLoopDesugar {
i = 0 and
child = SynthChild(SimpleParameterKind())
or
// block body
parent = block and
i = 1 and
child = SynthChild(BodyStmtKind())
or
exists(SimpleParameter param, BodyStmt body |
param = TSimpleParameterSynth(block, 0) and body = TBodyStmtSynth(block, 1)
|
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
parent = param and
i = 0 and
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
or
// assignment to pattern from for loop to synth parameter
parent = body and
i = 0 and
parent = block and
i = 1 and
child = SynthChild(AssignExprKind())
or
parent = TAssignExprSynth(body, 0) and
parent = TAssignExprSynth(block, 1) and
(
i = 0 and
child = childRef(for.getPattern())
@@ -1508,11 +1493,11 @@ private module ForLoopDesugar {
i = 1 and
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
)
or
// rest of block body
parent = body and
child = childRef(for.getBody().(Do).getStmt(i - 1))
)
or
// rest of block body
parent = block and
child = childRef(for.getBody().(Do).getStmt(i - 2))
)
)
)
@@ -1571,20 +1556,20 @@ private module ForLoopDesugar {
* { a: a }
* ```
*/
module ImplicitHashValueSynthesis {
Ruby::AstNode keyWithoutValue(Ruby::AstNode parent, int i) {
private module ImplicitHashValueSynthesis {
private Ruby::AstNode keyWithoutValue(AstNode parent, int i) {
exists(Ruby::KeywordPattern pair |
result = pair.getKey() and
result = parent.(Ruby::HashPattern).getChild(i).(Ruby::KeywordPattern).getKey() and
result = toGenerated(parent.(HashPattern).getKey(i)) and
not exists(pair.getValue())
)
or
parent =
any(Ruby::Pair pair |
i = 0 and
result = pair.getKey() and
not exists(pair.getValue())
)
exists(Ruby::Pair pair |
i = 0 and
result = pair.getKey() and
pair = toGenerated(parent) and
not exists(pair.getValue())
)
}
private string keyName(Ruby::AstNode key) {
@@ -1594,7 +1579,7 @@ module ImplicitHashValueSynthesis {
private class ImplicitHashValueSynthesis extends Synthesis {
final override predicate child(AstNode parent, int i, Child child) {
exists(Ruby::AstNode key | key = keyWithoutValue(toGenerated(parent), i) |
exists(Ruby::AstNode key | key = keyWithoutValue(parent, i) |
exists(TVariableReal variable |
access(key, variable) and
child = SynthChild(LocalVariableAccessRealKind(variable))
@@ -1621,7 +1606,7 @@ module ImplicitHashValueSynthesis {
}
final override predicate location(AstNode n, Location l) {
exists(AstNode p, int i | l = keyWithoutValue(toGenerated(p), i).getLocation() |
exists(AstNode p, int i | l = keyWithoutValue(p, i).getLocation() |
n = p.(HashPattern).getValue(i)
or
i = 0 and n = p.(Pair).getValue()
@@ -1966,31 +1951,3 @@ private module ImplicitSuperArgsSynthesis {
}
}
}
private module CallableBodySynthesis {
private predicate bodySynthesis(AstNode parent, int i, Child child) {
exists(TMethodBase m, Ruby::AstNode body |
body = any(Ruby::Method g | m = TMethod(g)).getBody()
or
body = any(Ruby::SingletonMethod g | m = TSingletonMethod(g)).getBody()
|
parent = m and
not body instanceof Ruby::BodyStatement and
i = 0 and
child = SynthChild(BodyStmtKind())
or
exists(Stmt bodyStmt |
parent = TBodyStmtSynth(m, 0) and
i = 0 and
bodyStmt = fromGenerated(body) and
child = childRef(bodyStmt)
)
)
}
private class CallableBodySynthesis extends Synthesis {
final override predicate child(AstNode parent, int i, Child child) {
bodySynthesis(parent, i, child)
}
}
}

View File

@@ -2,7 +2,6 @@ overlay[local]
module;
private import TreeSitter
private import codeql.namebinding.LocalNameBinding
private import codeql.ruby.AST
private import codeql.ruby.CFG
private import codeql.ruby.ast.internal.AST
@@ -95,11 +94,10 @@ predicate scopeDefinesParameterVariable(
// In case of overlapping parameter names (e.g. `_`), only the first
// parameter will give rise to a variable
i =
min(Ruby::Identifier other, int startline, int startcolumn |
parameterAssignment(scope, name, other, _) and
other.getLocation().hasLocationInfo(_, startline, startcolumn, _, _)
min(Ruby::Identifier other |
parameterAssignment(scope, name, other, _)
|
other order by startline, startcolumn
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
) and
parameterAssignment(scope, name, _, pos)
or
@@ -115,8 +113,7 @@ predicate scopeDefinesParameterVariable(
)
}
bindingset[i]
pragma[inline_late]
pragma[nomagic]
private string variableNameInScope(Ruby::AstNode i, Scope::Range scope) {
scope = scopeOf(i) and
(
@@ -140,142 +137,40 @@ private predicate scopeAssigns(Scope::Range scope, string name, Ruby::AstNode i)
name = variableNameInScope(i, scope)
}
private module Input implements LocalNameBindingInputSig<Location> {
predicate cacheRevRef() { exists(TVariable v) implies any() }
class AstNode = Ruby::AstNode;
AstNode getChild(AstNode parent, int index) {
parent = parentOf(result) and
(
index = result.getParentIndex()
or
not exists(result.getParentIndex()) and
index = -1
)
}
class Conditional extends AstNode {
Conditional() { none() }
AstNode getCondition() { none() }
AstNode getThen() { none() }
AstNode getElse() { none() }
}
class SiblingShadowingDecl extends AstNode {
SiblingShadowingDecl() { none() }
AstNode getLhs() { none() }
AstNode getRhs() { none() }
AstNode getElse() { none() }
}
predicate isTopScope(AstNode scope) {
scope instanceof Scope::Range and
not (
scope instanceof Ruby::Block or
scope instanceof Ruby::DoBlock or
scope instanceof Ruby::Lambda
)
}
private Scope::Range getParentScope(Scope::Range scope) {
result = scopeOf(scope) and
not isTopScope(scope)
}
bindingset[name, scope]
pragma[inline_late]
private predicate declInScope0(AstNode definingNode, string name, AstNode scope) {
scopeDefinesParameterVariable(scope, name, definingNode, _) or
scopeAssigns(scope, name, definingNode)
}
predicate declInScope(AstNode definingNode, string name, AstNode scope) {
scopeDefinesParameterVariable(scope, name, definingNode, _)
or
/*
* Variables are not declared explicitly in Ruby, so we consider the _first_ assignment to
* be the declaration:
*
* ```rb
* a = 1 # declares `a`
* a = 2 # does not declare `a`
* 1.times do | x | # declares `x`
* a = 2 # does not declare `a`
* end
* ```
*/
scopeAssigns(scope, name, definingNode) and
not scopeDefinesParameterVariable(scope, name, _, _) and
not exists(AstNode prev, AstNode prevScope |
prevScope = getParentScope*(scope) and
declInScope0(prev, name, prevScope) and
prev.getLocation().strictlyBefore(definingNode.getLocation())
)
}
predicate implicitDeclInScope(string name, AstNode scope) {
name = "self" and
scope instanceof SelfBase::Range
}
predicate accessCand(AstNode n, string name) {
name = variableNameInScope(n, _) and
(
explicitAssignmentNode(n, _)
or
implicitAssignmentNode(n)
or
scopeDefinesParameterVariable(_, _, n, _)
or
vcall(n)
or
n = any(Ruby::VariableReferencePattern vr).getName()
or
n = ImplicitHashValueSynthesis::keyWithoutValue(_, _)
)
or
n instanceof Ruby::Self and
name = "self"
}
}
private import LocalNameBinding<Location, Input>
cached
private module Cached {
cached
newtype TVariable =
TGlobalVariable(string name) {
CachedStage::ref() and
name = any(Ruby::GlobalVariable var).getValue()
} or
TGlobalVariable(string name) { name = any(Ruby::GlobalVariable var).getValue() } or
TClassVariable(Scope::Range scope, string name, Ruby::AstNode decl) {
decl =
min(Ruby::ClassVariable other, int startline, int startcolumn |
classVariableAccess(other, name, scope) and
other.getLocation().hasLocationInfo(_, startline, startcolumn, _, _)
min(Ruby::ClassVariable other |
classVariableAccess(other, name, scope)
|
other order by startline, startcolumn
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
)
} or
TInstanceVariable(Scope::Range scope, string name, boolean instance, Ruby::AstNode decl) {
decl =
min(Ruby::InstanceVariable other, int startline, int startcolumn |
instanceVariableAccess(other, name, scope, instance) and
other.getLocation().hasLocationInfo(_, startline, startcolumn, _, _)
min(Ruby::InstanceVariable other |
instanceVariableAccess(other, name, scope, instance)
|
other order by startline, startcolumn
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
)
} or
TLocalVariableReal(Local l) or
TLocalVariableReal(Scope::Range scope, string name, Ruby::AstNode i) {
scopeDefinesParameterVariable(scope, name, i, _)
or
i =
min(Ruby::AstNode other |
scopeAssigns(scope, name, other)
|
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
) and
not scopeDefinesParameterVariable(scope, name, _, _) and
not inherits(scope, name, _)
} or
TSelfVariable(SelfBase::Range scope) or
TLocalVariableSynth(AstNode n, int i) { any(Synthesis s).localVariable(n, i) }
// Db types that can be vcalls
@@ -426,37 +321,39 @@ private module Cached {
i = any(Ruby::ExpressionReferencePattern x).getValue()
}
pragma[nomagic]
private predicate hasScopeAndName(VariableReal variable, Scope::Range scope, string name) {
variable.getNameImpl() = name and
scope = variable.getDeclaringScopeImpl()
}
cached
predicate access(Ruby::AstNode access, VariableReal variable) {
exists(Local l |
variable = TLocalVariableReal(l) and
access = l.getAnAccess()
exists(string name, Scope::Range scope |
pragma[only_bind_into](name) = variableNameInScope(access, scope)
|
l instanceof ImplicitLocal
hasScopeAndName(variable, scope, name) and
not access.getLocation().strictlyBefore(variable.getLocationImpl()) and
// In case of overlapping parameter names, later parameters should not
// be considered accesses to the first parameter
if parameterAssignment(_, _, access, _)
then scopeDefinesParameterVariable(_, _, access, _)
else any()
or
/*
* In the example below, `a` is declared in the scope of `M`, but only the
* second mention of `a` is an actual access:
*
* ```rb
* module M
* puts a # calls method `a`
* a = 1 # declares `a`
* puts a # accesses variable `a`
* end
* ```
*/
not access.getLocation().strictlyBefore(l.getDefiningNode().getLocation())
exists(Scope::Range declScope |
hasScopeAndName(variable, declScope, pragma[only_bind_into](name)) and
inherits(scope, name, declScope)
)
)
}
private class Access extends Ruby::Token {
Access() {
access(this, _) or
access(this.(Ruby::Identifier), _) or
this instanceof Ruby::GlobalVariable or
this instanceof Ruby::InstanceVariable or
this instanceof Ruby::ClassVariable
this instanceof Ruby::ClassVariable or
this instanceof Ruby::Self
}
}
@@ -501,6 +398,29 @@ private module Cached {
import Cached
/** Holds if this scope inherits `name` from an outer scope `outer`. */
private predicate inherits(Scope::Range scope, string name, Scope::Range outer) {
(
scope instanceof Ruby::Block or
scope instanceof Ruby::DoBlock or
scope instanceof Ruby::Lambda
) and
not scopeDefinesParameterVariable(scope, name, _, _) and
(
outer = scope.getOuterScope() and
(
scopeDefinesParameterVariable(outer, name, _, _)
or
exists(Ruby::AstNode i |
scopeAssigns(outer, name, i) and
i.getLocation().strictlyBefore(scope.getLocation())
)
)
or
inherits(scope.getOuterScope(), name, outer)
)
}
abstract class VariableImpl extends TVariable {
abstract string getNameImpl();
@@ -509,9 +429,10 @@ abstract class VariableImpl extends TVariable {
abstract Location getLocationImpl();
}
class TVariableReal = TGlobalVariable or TClassVariable or TInstanceVariable or TLocalVariableReal;
class TVariableReal =
TGlobalVariable or TClassVariable or TInstanceVariable or TLocalVariableReal or TSelfVariable;
class TLocalVariable = TLocalVariableReal or TLocalVariableSynth;
class TLocalVariable = TLocalVariableReal or TLocalVariableSynth or TSelfVariable;
/**
* A "real" (i.e. non-synthesized) variable. This class only exists to
@@ -537,19 +458,19 @@ private class VariableRealAdapter extends VariableImpl, TVariableReal instanceof
}
class LocalVariableReal extends VariableReal, TLocalVariableReal {
private Local l;
private Scope::Range scope;
private string name;
private Ruby::AstNode i;
LocalVariableReal() { this = TLocalVariableReal(l) }
LocalVariableReal() { this = TLocalVariableReal(scope, name, i) }
Ruby::AstNode getDefiningNode() { result = l.getDefiningNode() }
final override string getNameImpl() { result = name }
final override string getNameImpl() { result = l.getName() }
final override Location getLocationImpl() { result = i.getLocation() }
final override Location getLocationImpl() { result = l.getLocation() }
final override Scope::Range getDeclaringScopeImpl() { result = scope }
final override Scope::Range getDeclaringScopeImpl() { result = l.getScope() }
final VariableAccess getDefiningAccessImpl() { toGenerated(result) = l.getDefiningNode() }
final VariableAccess getDefiningAccessImpl() { toGenerated(result) = i }
}
class LocalVariableSynth extends VariableImpl, TLocalVariableSynth {
@@ -610,16 +531,34 @@ class ClassVariableImpl extends VariableReal, TClassVariable {
final override Scope::Range getDeclaringScopeImpl() { result = scope }
}
class SelfVariableImpl extends LocalVariableReal {
private ImplicitLocal l;
class SelfVariableImpl extends VariableReal, TSelfVariable {
private SelfBase::Range scope;
SelfVariableImpl() { this = TLocalVariableReal(l) }
SelfVariableImpl() { this = TSelfVariable(scope) }
final override string getNameImpl() { result = "self" }
final override Location getLocationImpl() { result = scope.getLocation() }
final override Scope::Range getDeclaringScopeImpl() { result = scope }
}
abstract class VariableAccessImpl extends Expr, TVariableAccess {
abstract VariableImpl getVariableImpl();
}
module LocalVariableAccess {
predicate range(Ruby::Identifier id, TLocalVariableReal v) {
access(id, v) and
(
explicitWriteAccess(id, _) or
implicitWriteAccess(id) or
vcall(id) or
id = any(Ruby::VariableReferencePattern vr).getName()
)
}
}
class TVariableAccessReal =
TLocalVariableAccessReal or TGlobalVariableAccess or TInstanceVariableAccess or
TClassVariableAccess;
@@ -742,8 +681,7 @@ private class SelfVariableAccessReal extends SelfVariableAccessImpl, TSelfReal {
SelfVariableAccessReal() {
exists(Ruby::Self self |
this = TSelfReal(self) and
access(self, var)
this = TSelfReal(self) and var = TSelfVariable(scopeOf(self).getEnclosingSelfScope())
)
}

View File

@@ -100,26 +100,24 @@ private class EndBlockScope extends CfgScopeImpl, EndBlock {
}
}
private class CallableScope extends CfgScopeImpl, Callable {
private class BodyStmtCallableScope extends CfgScopeImpl, AstInternal::TBodyStmt, Callable {
final override predicate entry(AstNode first) { this.(Trees::BodyStmtTree).firstInner(first) }
final override predicate exit(AstNode last, Completion c) {
this.(Trees::BodyStmtTree).lastInner(last, c)
}
}
private class BraceBlockScope extends CfgScopeImpl, BraceBlock {
final override predicate entry(AstNode first) {
first(this.(Trees::CallableTree).getBodyChild(0), first)
first(this.(Trees::BraceBlockTree).getBodyChild(0, _), first)
}
final override predicate exit(AstNode last, Completion c) {
this.getBody().(Trees::BodyStmtTree).last(last, c)
last(this.(Trees::BraceBlockTree).getLastBodyChild(), last, c)
or
exists(int i |
not exists(this.getBody()) and
last(this.(Trees::CallableTree).getBodyChild(i), last, c) and
not exists(this.(Trees::CallableTree).getBodyChild(i + 1))
)
or
exists(AstNode child |
child = this.(Trees::CallableTree).getBodyChild(_) and
not child = this.getBody() and
last(child, last, c) and
not c instanceof NormalCompletion
)
last(this.(Trees::BraceBlockTree).getBodyChild(_, _), last, c) and
not c instanceof NormalCompletion
}
}
@@ -161,6 +159,10 @@ module Trees {
}
private class BeginTree extends BodyStmtTree instanceof BeginExpr {
final override predicate first(AstNode first) { this.firstInner(first) }
final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) }
final override predicate propagatesAbnormal(AstNode child) { none() }
}
@@ -194,21 +196,28 @@ module Trees {
private class BlockParameterTree extends NonDefaultValueParameterTree instanceof BlockParameter {
}
class BodyStmtTree extends StmtSequenceTree instanceof BodyStmt {
abstract class BodyStmtTree extends StmtSequenceTree instanceof BodyStmt {
/** Gets a rescue clause in this block. */
final RescueClause getARescue() { result = super.getRescue(_) }
/** Gets the `ensure` clause in this block, if any. */
final StmtSequence getEnsure() { result = super.getEnsure() }
override predicate first(AstNode first) {
override predicate first(AstNode first) { first = this }
predicate firstInner(AstNode first) {
first(this.getBodyChild(0, _), first)
or
not exists(this.getBodyChild(_, _)) and
first(super.getEnsure(), first)
(
first(super.getRescue(_), first)
or
not exists(super.getRescue(_)) and
first(super.getEnsure(), first)
)
}
override predicate last(AstNode last, Completion c) {
predicate lastInner(AstNode last, Completion c) {
exists(boolean ensurable | last = this.getAnEnsurePredecessor(c, ensurable) |
not super.hasEnsure()
or
@@ -378,28 +387,27 @@ module Trees {
private class BooleanLiteralTree extends LeafTree instanceof BooleanLiteral { }
class BraceBlockTree extends CallableTree instanceof BraceBlock {
final override AstNode getBodyChild(int i) {
result = super.getParameter(i)
or
result = super.getLocalVariable(i - super.getNumberOfParameters())
or
result = super.getBody() and
i = super.getNumberOfParameters() + count(super.getALocalVariable())
}
}
class CallableTree extends PostOrderTree instanceof Callable {
class BraceBlockTree extends StmtSequenceTree instanceof BraceBlock {
final override predicate propagatesAbnormal(AstNode child) { none() }
final override AstNode getBodyChild(int i, boolean rescuable) {
result = super.getParameter(i) and rescuable = false
or
result = super.getLocalVariable(i - super.getNumberOfParameters()) and rescuable = false
or
result =
StmtSequenceTree.super
.getBodyChild(i - super.getNumberOfParameters() - count(super.getALocalVariable()),
rescuable)
}
override predicate first(AstNode first) { first = this }
abstract AstNode getBodyChild(int i);
override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Normal left-to-right evaluation in the body
exists(int i |
last(this.getBodyChild(i), pred, c) and
first(this.getBodyChild(i + 1), succ) and
last(this.getBodyChild(i, _), pred, c) and
first(this.getBodyChild(i + 1, _), succ) and
c instanceof NormalCompletion
)
}
@@ -1008,16 +1016,20 @@ module Trees {
final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
}
private class DoBlockTree extends CallableTree instanceof DoBlock {
private class DoBlockTree extends BodyStmtTree instanceof DoBlock {
/** Gets the `i`th child in the body of this block. */
final override AstNode getBodyChild(int i) {
result = super.getParameter(i)
final override AstNode getBodyChild(int i, boolean rescuable) {
result = super.getParameter(i) and rescuable = false
or
result = super.getLocalVariable(i - super.getNumberOfParameters())
result = super.getLocalVariable(i - super.getNumberOfParameters()) and rescuable = false
or
result = super.getBody() and
i = super.getNumberOfParameters() + count(super.getALocalVariable())
result =
BodyStmtTree.super
.getBodyChild(i - super.getNumberOfParameters() - count(super.getALocalVariable()),
rescuable)
}
override predicate propagatesAbnormal(AstNode child) { none() }
}
private class EmptyStatementTree extends LeafTree instanceof EmptyStmt { }
@@ -1061,12 +1073,14 @@ module Trees {
final override AstNode getAccessNode() { result = super.getDefiningAccess() }
}
private class LambdaTree extends CallableTree instanceof Lambda {
private class LambdaTree extends BodyStmtTree instanceof Lambda {
final override predicate propagatesAbnormal(AstNode child) { none() }
/** Gets the `i`th child in the body of this block. */
final override AstNode getBodyChild(int i) {
result = super.getParameter(i)
final override AstNode getBodyChild(int i, boolean rescuable) {
result = super.getParameter(i) and rescuable = false
or
result = super.getBody() and i = super.getNumberOfParameters()
result = BodyStmtTree.super.getBodyChild(i - super.getNumberOfParameters(), rescuable)
}
}
@@ -1137,12 +1151,14 @@ module Trees {
private class MethodNameTree extends LeafTree instanceof MethodName, AstInternal::TTokenMethodName
{ }
private class MethodTree extends CallableTree instanceof Method {
private class MethodTree extends BodyStmtTree instanceof Method {
final override predicate propagatesAbnormal(AstNode child) { none() }
/** Gets the `i`th child in the body of this block. */
final override AstNode getBodyChild(int i) {
result = super.getParameter(i)
final override AstNode getBodyChild(int i, boolean rescuable) {
result = super.getParameter(i) and rescuable = false
or
result = super.getBody() and i = super.getNumberOfParameters()
result = BodyStmtTree.super.getBodyChild(i - super.getNumberOfParameters(), rescuable)
}
}
@@ -1167,12 +1183,12 @@ module Trees {
BodyStmtTree.super.succ(pred, succ, c)
or
pred = this and
super.first(succ) and
this.firstInner(succ) and
c instanceof SimpleCompletion
}
final override predicate last(AstNode last, Completion c) {
super.last(last, c)
this.lastInner(last, c)
or
not exists(this.getAChild(_)) and
last = this and
@@ -1312,7 +1328,7 @@ module Trees {
private class SingletonClassTree extends BodyStmtTree instanceof SingletonClass {
final override predicate first(AstNode first) {
super.first(first)
this.firstInner(first)
or
not exists(this.getAChild(_)) and
first = this
@@ -1322,12 +1338,7 @@ module Trees {
BodyStmtTree.super.succ(pred, succ, c)
or
succ = this and
super.last(pred, c)
}
final override predicate last(AstNode last, Completion c) {
last = this and
c.isValidFor(this)
this.lastInner(pred, c)
}
/** Gets the `i`th child in the body of this block. */
@@ -1340,18 +1351,20 @@ module Trees {
}
}
private class SingletonMethodTree extends CallableTree instanceof SingletonMethod {
private class SingletonMethodTree extends BodyStmtTree instanceof SingletonMethod {
final override predicate propagatesAbnormal(AstNode child) { none() }
/** Gets the `i`th child in the body of this block. */
final override AstNode getBodyChild(int i) {
result = super.getParameter(i)
final override AstNode getBodyChild(int i, boolean rescuable) {
result = super.getParameter(i) and rescuable = false
or
result = super.getBody() and i = super.getNumberOfParameters()
result = BodyStmtTree.super.getBodyChild(i - super.getNumberOfParameters(), rescuable)
}
override predicate first(AstNode first) { first(super.getObject(), first) }
override predicate succ(AstNode pred, AstNode succ, Completion c) {
CallableTree.super.succ(pred, succ, c)
BodyStmtTree.super.succ(pred, succ, c)
or
last(super.getObject(), pred, c) and
succ = this and
@@ -1430,6 +1443,10 @@ module Trees {
or
result = BodyStmtTree.super.getBodyChild(i - count(super.getABeginBlock()), rescuable)
}
final override predicate first(AstNode first) { super.firstInner(first) }
final override predicate last(AstNode last, Completion c) { super.lastInner(last, c) }
}
private class UndefStmtTree extends StandardPreOrderTree instanceof UndefStmt {

View File

@@ -246,7 +246,7 @@ module EnsureSplitting {
private predicate exit0(AstNode pred, Trees::BodyStmtTree block, int nestLevel, Completion c) {
this.appliesToPredecessor(pred) and
nestLevel = block.getNestLevel() and
block.last(pred, c)
block.lastInner(pred, c)
}
/**

View File

@@ -1662,7 +1662,7 @@ private module ReturnNodes {
* last thing that is evaluated in the body of the callable.
*/
class ExprReturnNode extends SourceReturnNode, ExprNode {
ExprReturnNode() { exists(Callable c | implicitReturn(c, this) = c.getBody().getAStmt()) }
ExprReturnNode() { exists(Callable c | implicitReturn(c, this) = c.getAStmt()) }
override ReturnKind getKindSource() {
exists(CfgScope scope | scope = this.(NodeImpl).getCfgScope() |

View File

@@ -1392,7 +1392,7 @@ class StmtSequenceNode extends ExprNode {
/**
* A data flow node corresponding to a method, block, or lambda expression.
*/
class CallableNode extends ExprNode {
class CallableNode extends StmtSequenceNode {
private Callable callable;
CallableNode() { this.asExpr().getExpr() = callable }

View File

@@ -83,7 +83,11 @@ module Rbi {
/**
* Gets the type aliased by this call.
*/
RbiType getAliasedType() { result = this.getBlock().getBody().getLastStmt() }
RbiType getAliasedType() {
exists(ExprNodes::MethodCallCfgNode n | n.getExpr() = this |
result = n.getBlock().(ExprNodes::StmtSequenceCfgNode).getLastStmt().getExpr()
)
}
}
/**
@@ -300,7 +304,7 @@ module Rbi {
private MethodSignatureCall sigCall;
MethodSignatureDefiningCall() {
exists(MethodCall c | c = sigCall.getBlock().getBody().getAChild() |
exists(MethodCall c | c = sigCall.getBlock().getAChild() |
// The typical pattern for the contents of a `sig` block is something
// like `params(<param defs>).returns(<return type>)` - we want to
// pick up both of these calls.

View File

@@ -18,7 +18,7 @@ module Slim {
override DataFlow::Node getTemplate() {
result.asExpr().getExpr() =
this.getBlock().(DataFlow::BlockNode).asCallableAstNode().getBody().getAStmt()
this.getBlock().(DataFlow::BlockNode).asCallableAstNode().getAStmt()
}
}

View File

@@ -38,7 +38,6 @@ private class NokogiriXmlParserCall extends XmlParserCall::Range, DataFlow::Call
.getExpr()
.(MethodCall)
.getBlock()
.getBody()
.getAStmt()
.getAChild*()
.(MethodCall)

View File

@@ -98,7 +98,7 @@ module Routing {
Block getBlock() { result = block }
override Stmt getAStmt() { result = block.getBody().getAStmt() }
override Stmt getAStmt() { result = block.getAStmt() }
override RouteBlock getParent() { none() }
@@ -128,7 +128,7 @@ module Routing {
override string getAPrimaryQlClass() { result = "ConstraintsRouteBlock" }
override Stmt getAStmt() { result = block.getBody().getAStmt() }
override Stmt getAStmt() { result = block.getAStmt() }
override string getPathComponent() { result = "" }
@@ -156,7 +156,7 @@ module Routing {
override string getAPrimaryQlClass() { result = "ScopeRouteBlock" }
override Stmt getAStmt() { result = block.getBody().getAStmt() }
override Stmt getAStmt() { result = block.getAStmt() }
override string toString() { result = methodCall.toString() }
@@ -216,7 +216,7 @@ module Routing {
override string getAPrimaryQlClass() { result = "ResourcesRouteBlock" }
override Stmt getAStmt() { result = block.getBody().getAStmt() }
override Stmt getAStmt() { result = block.getAStmt() }
/**
* Gets the `resources` call that gives rise to this route block.
@@ -282,7 +282,7 @@ module Routing {
NamespaceRouteBlock() { this = TNamespaceRouteBlock(parent, methodCall, block) }
override Stmt getAStmt() { result = block.getBody().getAStmt() }
override Stmt getAStmt() { result = block.getAStmt() }
override string getPathComponent() { result = this.getNamespace() }

View File

@@ -70,7 +70,7 @@ private predicate memoReturnedFromMethod(Method m, MemoStmt s) {
or
// If we don't have flow (e.g. due to the dataflow library not supporting instance variable flow yet),
// fall back to a syntactic heuristic: does the last statement in the method mention the memoization variable?
m.getBody().getLastStmt().getAChild*().(InstanceVariableReadAccess).getVariable() =
m.getLastStmt().getAChild*().(InstanceVariableReadAccess).getVariable() =
s.getVariableAccess().getVariable()
}

View File

@@ -33,7 +33,7 @@ private class SourceCall extends RelevantGemCall {
private class GitSourceCall extends RelevantGemCall {
GitSourceCall() { this.getMethodName() = "git_source" }
override Expr getAUrlPart() { result = this.getBlock().getBody().getLastStmt() }
override Expr getAUrlPart() { result = this.getBlock().getLastStmt() }
}
/**

Some files were not shown because too many files have changed in this diff Show More