mirror of
https://github.com/github/codeql.git
synced 2026-05-29 18:41:27 +02:00
Compare commits
2 Commits
copilot/fi
...
idrissrio/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cc361bf35 | ||
|
|
749a40153d |
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
6
cpp/ql/lib/semmle/code/cpp/Member.qll
Normal file
6
cpp/ql/lib/semmle/code/cpp/Member.qll
Normal 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
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
| common.h:5:12:5:18 | myArray | int[1] |
|
||||
| common.h:5:12:5:18 | myArray | int[8] |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -69,7 +69,6 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
}
|
||||
|
||||
Overrides(trapFile);
|
||||
ExtractRefReturn(trapFile, Symbol, this);
|
||||
|
||||
if (Symbol.FromSource() && !HasBody)
|
||||
{
|
||||
|
||||
@@ -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
100
csharp/paket.lock
generated
@@ -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
31
csharp/paket.main.bzl
generated
File diff suppressed because one or more lines are too long
@@ -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 |
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved call target resolution for ref-return properties and indexers.
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class SBCS
|
||||
class SBCS
|
||||
{
|
||||
string sbcs = "<22>";
|
||||
string sbcs = "<22>";
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
| Prop.field |
|
||||
| caption |
|
||||
| next |
|
||||
| x |
|
||||
| y |
|
||||
| z |
|
||||
|
||||
@@ -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 |
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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()]);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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 |
|
||||
@@ -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
|
||||
@@ -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 |
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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 |
|
||||
@@ -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()
|
||||
@@ -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 |
|
||||
@@ -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()
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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 |
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() |
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ private class NokogiriXmlParserCall extends XmlParserCall::Range, DataFlow::Call
|
||||
.getExpr()
|
||||
.(MethodCall)
|
||||
.getBlock()
|
||||
.getBody()
|
||||
.getAStmt()
|
||||
.getAChild*()
|
||||
.(MethodCall)
|
||||
|
||||
@@ -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() }
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user