Merge pull request #14363 from michaelnebel/csharp/sunsetqlstubgen

C#: Sunset QL based stub generator.
This commit is contained in:
Michael Nebel
2023-10-24 11:05:56 +02:00
committed by GitHub
20 changed files with 6 additions and 1537 deletions

View File

@@ -91,7 +91,7 @@ jobs:
run: |
# Generate (Asp)NetCore stubs
STUBS_PATH=stubs_output
python3 ql/src/Stubs/make_stubs_nuget.py webapp Swashbuckle.AspNetCore.Swagger 6.5.0 "$STUBS_PATH"
python3 scripts/stubs/make_stubs_nuget.py webapp Swashbuckle.AspNetCore.Swagger 6.5.0 "$STUBS_PATH"
rm -rf ql/test/resources/stubs/_frameworks
# Update existing stubs in the repo with the freshly generated ones
mv "$STUBS_PATH/output/stubs/_frameworks" ql/test/resources/stubs/

View File

@@ -1,15 +0,0 @@
/**
* Tool to generate C# stubs from a qltest snapshot.
*/
import csharp
import Stubs
/** All public declarations from assemblies. */
class AllExternalPublicDeclarations extends GeneratedDeclaration {
AllExternalPublicDeclarations() { this.fromLibrary() }
}
from Assembly a
select a.getFullName(), a.getName(), a.getVersion().toString(), a.getFile().getAbsolutePath(),
generatedCode(a)

View File

@@ -1,22 +0,0 @@
/**
* Tool to generate C# stubs from a qltest snapshot.
*/
import csharp
import Stubs
/** All public declarations from source. */
class AllDeclarations extends GeneratedDeclaration {
AllDeclarations() { not this.fromLibrary() }
}
/** Exclude types from these standard assemblies. */
private class DefaultLibs extends ExcludedAssembly {
DefaultLibs() {
this.getName() = "System.Private.CoreLib" or
this.getName() = "mscorlib" or
this.getName() = "System.Runtime"
}
}
select concat(generatedCode(_) + "\n\n")

View File

@@ -1,41 +0,0 @@
/**
* Tool to generate C# stubs from a qltest snapshot.
*
* It finds all declarations used in the source code,
* and generates minimal C# stubs containing those declarations
* and their dependencies.
*/
import csharp
import Stubs
/** Declarations used by source code. */
class UsedInSource extends GeneratedDeclaration {
UsedInSource() {
(
this = any(Access a).getTarget()
or
this = any(Call c).getTarget()
or
this = any(TypeMention tm).getType()
or
exists(Virtualizable v | v.fromSource() |
this = v.getImplementee() or this = v.getOverridee()
)
or
this = any(Attribute a).getType().getAConstructor()
) and
this.fromLibrary()
}
}
/** Exclude types from these standard assemblies. */
private class DefaultLibs extends ExcludedAssembly {
DefaultLibs() {
this.getName() = "System.Private.CoreLib" or
this.getName() = "mscorlib" or
this.getName() = "System.Runtime"
}
}
select concat(generatedCode(_) + "\n\n")

View File

@@ -1,965 +0,0 @@
/**
* Generates C# stubs for use in test code.
*
* Extend the abstract class `GeneratedDeclaration` with the declarations that should be generated.
* This will generate stubs for all the required dependencies as well.
*
* Use
* ```ql
* select generatedCode()
* ```
* to retrieve the generated C# code.
*/
import csharp
private import semmle.code.csharp.commons.QualifiedName
private import semmle.code.csharp.frameworks.System
private import semmle.code.dotnet.DotNet as DotNet // added to handle VoidType as a ValueOrRefType
/** An element that should be in the generated code. */
abstract class GeneratedElement extends Element { }
/** A member that should be in the generated code. */
abstract class GeneratedMember extends Member, GeneratedElement { }
/** Class representing all `struct`s, such as user defined ones and built-in ones, like `int`. */
private class StructExt extends Type {
StructExt() {
this instanceof Struct or
this instanceof SimpleType or
this instanceof VoidType or
this instanceof SystemIntPtrType
}
}
/** A type that should be in the generated code. */
abstract private class GeneratedType extends Type, GeneratedElement {
GeneratedType() {
(
this instanceof Interface
or
this instanceof Class
or
this instanceof StructExt
or
this instanceof Enum
or
this instanceof DelegateType
) and
not this instanceof ConstructedType and
not this.getALocation() instanceof ExcludedAssembly
}
/**
* Holds if this type is defined in multiple assemblies, and at least one of
* them is in the `Microsoft.NETCore.App.Ref` folder. In this case, we only stub
* the type in the assembly in `Microsoft.NETCore.App.Ref`. In case there are
* multiple assemblies in this folder, then we prefer `System.Runtime`.
*/
private predicate isDuplicate(Assembly assembly) {
// type exists in multiple assemblies
count(this.getALocation().(Assembly)) > 1 and
// at least one of them is in the `Microsoft.NETCore.App.Ref` folder
this.getALocation()
.(Assembly)
.getFile()
.getAbsolutePath()
.matches("%Microsoft.NETCore.App.Ref%") and
exists(int i |
i =
count(Assembly a |
this.getALocation() = a and
a.getFile().getAbsolutePath().matches("%Microsoft.NETCore.App.Ref%")
)
|
i = 1 and
// assemblies not in `Microsoft.NETCore.App.Ref` folder are considered duplicates
not assembly.getFile().getAbsolutePath().matches("%Microsoft.NETCore.App.Ref%")
or
i > 1 and
// one of the assemblies is named `System.Runtime`
this.getALocation().(Assembly).getName() = "System.Runtime" and
// all others are considered duplicates
assembly.getName() != "System.Runtime"
)
}
predicate isInAssembly(Assembly assembly) { this.getALocation() = assembly }
private string stubKeyword() {
this instanceof Interface and result = "interface"
or
this instanceof StructExt and result = "struct"
or
this instanceof Class and result = "class"
or
this instanceof Enum and result = "enum"
or
this instanceof DelegateType and result = "delegate"
}
private string stubAbstractModifier() {
if this.(Class).isAbstract() then result = "abstract " else result = ""
}
private string stubStaticModifier() {
if this.isStatic() then result = "static " else result = ""
}
private string stubPartialModifier() {
if
count(Assembly a | this.getALocation() = a) <= 1 or
this instanceof Enum
then result = ""
else result = "partial "
}
private string stubAttributes() {
if this.(ValueOrRefType).getAnAttribute().getType().hasQualifiedName("System", "FlagsAttribute")
then result = "[System.Flags]\n"
else result = ""
}
/** Gets the entire C# stub code for this type. */
pragma[nomagic]
final string getStub(Assembly assembly) {
this.isInAssembly(assembly) and
if this.isDuplicate(assembly)
then
result =
"/* Duplicate type '" + this.getName() + "' is not stubbed in this assembly '" +
assembly.toString() + "'. */\n\n"
else (
not this instanceof DelegateType and
result =
this.stubAttributes() + stubUnsafe(this) + stubAccessibility(this) +
this.stubAbstractModifier() + this.stubStaticModifier() + this.stubPartialModifier() +
this.stubKeyword() + " " + this.getUndecoratedName() + stubGenericArguments(this) +
this.stubBaseTypesString() + stubTypeParametersConstraints(this) + "\n{\n" +
this.stubPrivateConstructor() + this.stubMembers(assembly) + "}\n\n"
or
result =
this.stubAttributes() + stubUnsafe(this) + stubAccessibility(this) + this.stubKeyword() +
" " + stubClassName(this.(DelegateType).getReturnType()) + " " + this.getUndecoratedName()
+ stubGenericArguments(this) + "(" + stubParameters(this) + ");\n\n"
)
}
private ValueOrRefType getAnInterestingBaseType() {
result = this.(ValueOrRefType).getABaseType() and
not result instanceof ObjectType and
not result.hasQualifiedName("System", "ValueType") and
(not result instanceof Interface or result.(Interface).isEffectivelyPublic())
}
private string stubBaseTypesString() {
if this instanceof Enum
then result = " : " + this.(Enum).getUnderlyingType().toStringWithTypes()
else
if exists(this.getAnInterestingBaseType())
then
result =
" : " +
concat(int i, ValueOrRefType t |
t = this.getAnInterestingBaseType() and
(if t instanceof Class then i = 0 else i = 1)
|
stubClassName(t), ", " order by i, t.getQualifiedName()
)
else result = ""
}
language[monotonicAggregates]
private string stubMembers(Assembly assembly) {
result =
concat(GeneratedMember m |
m = this.getAGeneratedMember(assembly)
|
stubMember(m, assembly)
order by
m.getQualifiedNameWithTypes(), stubExplicitImplementation(m)
)
}
string stubPrivateConstructor() {
if
this instanceof Interface
or
this.isStatic()
or
this.isAbstract()
or
exists(this.(ValueOrRefType).getAConstructor())
or
not exists(this.getAnInterestingBaseType())
or
not exists(this.getAnInterestingBaseType().getAConstructor())
or
exists(Constructor bc |
bc = this.getAnInterestingBaseType().getAConstructor() and
bc.getNumberOfParameters() = 0 and
not bc.isStatic()
)
then result = ""
else
result =
" private " + this.getUndecoratedName() + "() : base(" +
stubDefaultArguments(getBaseConstructor(this), this) + ")" + " => throw null;\n"
}
private GeneratedMember getAGeneratedMember() { result.getDeclaringType() = this }
pragma[noinline]
private GeneratedMember getAGeneratedMember(Assembly assembly) {
result = this.getAGeneratedMember() and assembly = result.getALocation()
}
final Type getAGeneratedType() {
result = this.getAnInterestingBaseType()
or
result = this.getAGeneratedMember().(Callable).getReturnType()
or
result = this.getAGeneratedMember().(Callable).getAParameter().getType()
or
result = this.getAGeneratedMember().(Property).getType()
or
result = this.getAGeneratedMember().(Field).getType()
}
}
/**
* A declaration that should be generated.
* This is extended in client code to identify the actual
* declarations that should be generated.
*/
abstract class GeneratedDeclaration extends Modifiable {
GeneratedDeclaration() { this.isEffectivelyPublic() }
}
private class IndirectType extends GeneratedType {
IndirectType() {
this.(ValueOrRefType).getASubType() instanceof GeneratedType
or
this.(ValueOrRefType).getAChildType() instanceof GeneratedType
or
this.(UnboundGenericType).getAConstructedGeneric().getASubType() instanceof GeneratedType
or
exists(GeneratedType t |
this = getAContainedType(t.getAGeneratedType()).getUnboundDeclaration()
)
or
exists(GeneratedDeclaration decl |
decl.(Member).getDeclaringType().getUnboundDeclaration() = this
)
}
}
private class RootGeneratedType extends GeneratedType {
RootGeneratedType() { this = any(GeneratedDeclaration decl).getUnboundDeclaration() }
}
private Type getAContainedType(Type t) {
result = t
or
result = getAContainedType(t.(ConstructedType).getATypeArgument())
}
private class RootGeneratedMember extends GeneratedMember {
RootGeneratedMember() { this = any(GeneratedDeclaration d).getUnboundDeclaration() }
}
private predicate declarationExists(Virtualizable m) {
m instanceof GeneratedMember
or
m.getLocation() instanceof ExcludedAssembly
}
private class InheritedMember extends GeneratedMember, Virtualizable {
InheritedMember() {
declarationExists(this.getImplementee+())
or
declarationExists(this.getAnImplementor+())
or
declarationExists(this.getOverridee+())
or
declarationExists(this.getAnOverrider+())
}
}
private class ExtraGeneratedConstructor extends GeneratedMember, Constructor {
ExtraGeneratedConstructor() {
not this.isStatic() and
not this.isEffectivelyPublic() and
this.getDeclaringType() instanceof GeneratedType and
(
// if the base class has no 0 parameter constructor
not exists(Constructor c |
c = this.getDeclaringType().getBaseClass().getAMember() and
c.getNumberOfParameters() = 0 and
not c.isStatic()
)
or
// if this constructor might be called from a (generic) derived class
exists(Class c |
this.getDeclaringType() = c.getBaseClass().getUnboundDeclaration() and
this = getBaseConstructor(c).getUnboundDeclaration()
)
)
}
}
/** A namespace that contains at least one generated type. */
private class GeneratedNamespace extends Namespace, GeneratedElement {
GeneratedNamespace() {
this.getATypeDeclaration() instanceof GeneratedType
or
this.getAChildNamespace() instanceof GeneratedNamespace
}
private string getPreamble() {
if this.isGlobalNamespace()
then result = ""
else result = "namespace " + this.getName() + "\n{\n"
}
private string getPostAmble() { if this.isGlobalNamespace() then result = "" else result = "}\n" }
final string getStubs(Assembly assembly) {
result =
this.getPreamble() + this.getTypeStubs(assembly) + this.getSubNamespaceStubs(assembly) +
this.getPostAmble()
}
/** Gets the `n`th generated child namespace, indexed from 0. */
pragma[nomagic]
final GeneratedNamespace getChildNamespace(int n) {
result.getParentNamespace() = this and
result.getName() =
rank[n + 1](GeneratedNamespace g | g.getParentNamespace() = this | g.getName())
}
final int getChildNamespaceCount() {
result = count(GeneratedNamespace g | g.getParentNamespace() = this)
}
private predicate isInAssembly(Assembly assembly) {
any(GeneratedType gt | gt.(DotNet::ValueOrRefType).getDeclaringNamespace() = this)
.isInAssembly(assembly)
or
this.getChildNamespace(_).isInAssembly(assembly)
}
language[monotonicAggregates]
string getSubNamespaceStubs(Assembly assembly) {
this.isInAssembly(assembly) and
result =
concat(GeneratedNamespace child, int i |
child = this.getChildNamespace(i) and child.isInAssembly(assembly)
|
child.getStubs(assembly) order by i
)
}
string getTypeStubs(Assembly assembly) {
this.isInAssembly(assembly) and
result =
concat(GeneratedType gt |
gt.(DotNet::ValueOrRefType).getDeclaringNamespace() = this and gt.isInAssembly(assembly)
|
gt.getStub(assembly) order by gt.getName()
)
}
}
/**
* Specify assemblies to exclude.
* Do not generate any types from these assemblies.
*/
abstract class ExcludedAssembly extends Assembly { }
private Virtualizable getAccessibilityDeclaringVirtualizable(Virtualizable v) {
if not v.isOverride()
then result = v
else
if not v.getOverridee().getLocation() instanceof ExcludedAssembly
then result = getAccessibilityDeclaringVirtualizable(v.getOverridee())
else result = v
}
private string stubAccessibility(Member m) {
if
m.getDeclaringType() instanceof Interface
or
exists(getExplicitImplementedInterface(m))
or
m instanceof Constructor and m.isStatic()
then result = ""
else
if m.isPublic()
then result = "public "
else
if m.isProtected()
then
if m.isPrivate() or getAccessibilityDeclaringVirtualizable(m).isPrivate()
then result = "protected private "
else
if m.isInternal() or getAccessibilityDeclaringVirtualizable(m).isInternal()
then result = "protected internal "
else result = "protected "
else
if m.isPrivate()
then result = "private "
else
if m.isInternal()
then result = "internal "
else result = "unknown-accessibility"
}
private string stubModifiers(Member m) {
result = stubUnsafe(m) + stubAccessibility(m) + stubStaticOrConst(m) + stubOverride(m)
}
private string stubUnsafe(Member m) {
if m.(Modifiable).isUnsafe() then result = "unsafe " else result = ""
}
private string stubStaticOrConst(Member m) {
if m.(Modifiable).isStatic()
then result = "static "
else
if m.(Modifiable).isConst()
then result = "const "
else result = ""
}
private string stubOverride(Member m) {
if m.getDeclaringType() instanceof Interface and not m.isStatic()
then result = ""
else
if m.(Virtualizable).isVirtual()
then result = "virtual "
else
if m.(Virtualizable).isAbstract()
then
if m.(Virtualizable).isOverride()
then result = "abstract override "
else result = "abstract "
else
if m.(Virtualizable).isOverride()
then result = "override "
else result = ""
}
private string stubQualifiedNamePrefix(ValueOrRefType t) {
if t.getParent() instanceof GlobalNamespace
then result = ""
else
if t.getParent() instanceof Namespace
then result = t.getDeclaringNamespace().getFullName() + "."
else result = stubClassName(t.getDeclaringType()) + "."
}
language[monotonicAggregates]
private string stubClassName(Type t) {
if t instanceof ObjectType
then result = "object"
else
if t instanceof StringType
then result = "string"
else
if t instanceof IntType
then result = "int"
else
if t instanceof BoolType
then result = "bool"
else
if t instanceof VoidType
then result = "void"
else
if t instanceof FloatType
then result = "float"
else
if t instanceof DoubleType
then result = "double"
else
if t instanceof NullableType
then result = stubClassName(t.(NullableType).getUnderlyingType()) + "?"
else
if t instanceof TypeParameter
then result = t.getName()
else
if t instanceof ArrayType
then result = stubClassName(t.(ArrayType).getElementType()) + "[]"
else
if t instanceof PointerType
then result = stubClassName(t.(PointerType).getReferentType()) + "*"
else
if t instanceof TupleType
then
exists(TupleType tt | tt = t |
if tt.getArity() < 2
then result = stubClassName(tt.getUnderlyingType())
else
result =
"(" +
concat(int i, Type element |
element = tt.getElementType(i)
|
stubClassName(element), "," order by i
) + ")"
)
else
if t instanceof FunctionPointerType
then
exists(
FunctionPointerType fpt, CallingConvention callconvention,
string calltext
|
fpt = t
|
callconvention = fpt.getCallingConvention() and
(
if callconvention instanceof UnmanagedCallingConvention
then calltext = "unmanaged"
else calltext = ""
) and
result =
"delegate* " + calltext + "<" +
concat(int i, Parameter p |
p = fpt.getParameter(i)
|
stubClassName(p.getType()) + "," order by i
) + stubClassName(fpt.getReturnType()) + ">"
)
else
if t instanceof ValueOrRefType
then
result =
stubQualifiedNamePrefix(t) + t.getUndecoratedName() +
stubGenericArguments(t)
else result = "<error>"
}
language[monotonicAggregates]
private string stubGenericArguments(Type t) {
if t instanceof UnboundGenericType
then
result =
"<" +
concat(int n |
exists(t.(UnboundGenericType).getTypeParameter(n))
|
t.(UnboundGenericType).getTypeParameter(n).getName(), "," order by n
) + ">"
else
if t instanceof ConstructedType
then
result =
"<" +
concat(int n |
exists(t.(ConstructedType).getTypeArgument(n))
|
stubClassName(t.(ConstructedType).getTypeArgument(n)), "," order by n
) + ">"
else result = ""
}
private string stubGenericMethodParams(Method m) {
if m instanceof UnboundGenericMethod
then
result =
"<" +
concat(int n, TypeParameter param |
param = m.(UnboundGenericMethod).getTypeParameter(n)
|
param.getName(), "," order by n
) + ">"
else result = ""
}
private string stubConstraints(TypeParameterConstraints tpc, int i) {
tpc.hasConstructorConstraint() and result = "new()" and i = 4
or
tpc.hasUnmanagedTypeConstraint() and result = "unmanaged" and i = 0
or
tpc.hasValueTypeConstraint() and
result = "struct" and
i = 0 and
not tpc.hasUnmanagedTypeConstraint() and
not stubClassName(tpc.getATypeConstraint().(Class)) = "System.Enum"
or
tpc.hasRefTypeConstraint() and result = "class" and i = 0
or
result = tpc.getATypeConstraint().(TypeParameter).getName() and i = 3
or
result = stubClassName(tpc.getATypeConstraint().(Interface)) and i = 2
or
result = stubClassName(tpc.getATypeConstraint().(Class)) and i = 1
}
private string stubTypeParameterConstraints(TypeParameter tp) {
if
tp.getDeclaringGeneric().(Virtualizable).isOverride() or
tp.getDeclaringGeneric().(Virtualizable).implementsExplicitInterface()
then
if tp.getConstraints().hasValueTypeConstraint()
then result = " where " + tp.getName() + ": struct"
else
if tp.getConstraints().hasRefTypeConstraint()
then result = " where " + tp.getName() + ": class"
else result = ""
else
exists(TypeParameterConstraints tpc | tpc = tp.getConstraints() |
result =
" where " + tp.getName() + ": " +
strictconcat(string s, int i | s = stubConstraints(tpc, i) | s, ", " order by i)
)
}
private string stubTypeParametersConstraints(Declaration d) {
if d instanceof UnboundGeneric
then
result =
concat(TypeParameter tp |
tp = d.(UnboundGeneric).getATypeParameter()
|
stubTypeParameterConstraints(tp), " "
)
else result = ""
}
private string stubImplementation(Virtualizable c) {
if c.isAbstract() then result = "" else result = " => throw null"
}
private predicate isKeyword(string s) {
s =
[
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked",
"class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else",
"enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach",
"goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long",
"namespace", "new", "null", "object", "operator", "out", "override", "params", "private",
"protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof",
"stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try",
"typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void",
"volatile", "while"
]
}
bindingset[s]
private string escapeIfKeyword(string s) { if isKeyword(s) then result = "@" + s else result = s }
private string stubParameters(Parameterizable p) {
result =
concat(int i, Parameter param |
param = p.getParameter(i) and not param.getType() instanceof ArglistType
|
stubParameterModifiers(param) + stubClassName(param.getType()) + " " +
escapeIfKeyword(param.getName()) + stubDefaultValue(param), ", "
order by
i
)
}
private string stubDefaultArguments(Constructor baseCtor, ValueOrRefType callingType) {
baseCtor = getBaseConstructor(callingType) and
baseCtor.getNumberOfParameters() > 0 and
result =
concat(int i, Parameter param |
param = baseCtor.getParameter(i) and not param.getType() instanceof ArglistType
|
"default(" + stubClassName(param.getType()) + ")", ", " order by i
)
}
private string stubParameterModifiers(Parameter p) {
if p.isOut()
then result = "out "
else
if p.isRef()
then result = "ref "
else
if p.isParams()
then result = "params "
else
if p.isIn()
then result = "" // Only C# 7.1 so ignore
else
if p.hasExtensionMethodModifier()
then result = "this "
else result = ""
}
private string stubDefaultValue(Parameter p) {
if p.hasDefaultValue()
then result = " = default(" + stubClassName(p.getType()) + ")"
else result = ""
}
private string stubEventAccessors(Event e) {
if exists(e.(Virtualizable).getExplicitlyImplementedInterface())
then result = " { add => throw null; remove => throw null; }"
else result = ";"
}
/**
* Returns an interface that `c` explicitly implements, if either of the
* following also holds.
* (1) `c` is not static.
* (2) `c` is static and an implementation of a generic with type constraints.
* (3) `c` is static and there is another member with the same name
* but different return type.
*
* We use these rules as explicit interfaces are needed in some cases
* for compilation purposes (both to distinguish members but also to ensure
* type constraints are satisfied). We can't always use explicit interface
* implementation due to the generic math support, because then in some cases
* we will only be able to access a static via a type variable with type
* constraints (C# 11 language feature).
*/
private Interface getExplicitImplementedInterface(Virtualizable c) {
result = unique(Interface i | i = c.getExplicitlyImplementedInterface()) and
(
not c.isStatic()
or
c.isStatic() and
(
not c instanceof Method
or
c instanceof Method and
(
exists(TypeParameter t | t = c.getImplementee().(UnboundGeneric).getATypeParameter() |
exists(t.getConstraints().getATypeConstraint())
)
or
exists(Member m |
(not m.isStatic() or m.(Method).getReturnType() != c.(Method).getReturnType()) and
m.getName() = c.getName() and
m.getDeclaringType() = c.getDeclaringType()
)
)
)
)
}
private string stubExplicitImplementation(Member c) {
if exists(getExplicitImplementedInterface(c))
then result = stubClassName(getExplicitImplementedInterface(c)) + "."
else result = ""
}
pragma[noinline]
private string stubMethod(Method m, Assembly assembly) {
m instanceof GeneratedMember and
m.getALocation() = assembly and
if not m.getDeclaringType() instanceof Enum
then
result =
" " + stubModifiers(m) + stubClassName(m.getReturnType()) + " " +
stubExplicitImplementation(m) + escapeIfKeyword(m.getUndecoratedName()) +
stubGenericMethodParams(m) + "(" + stubParameters(m) + ")" +
stubTypeParametersConstraints(m) + stubImplementation(m) + ";\n"
else result = " // Stub generator skipped method: " + m.getName() + "\n"
}
pragma[noinline]
private string stubOperator(Operator o, Assembly assembly) {
o instanceof GeneratedMember and
o.getALocation() = assembly and
if o instanceof ConversionOperator
then
result =
" " + stubModifiers(o) + stubExplicit(o) + stubExplicitImplementation(o) + "operator " +
stubChecked(o) + stubClassName(o.getReturnType()) + "(" + stubParameters(o) + ")" +
stubImplementation(o) + ";\n"
else
if not o.getDeclaringType() instanceof Enum
then
result =
" " + stubModifiers(o) + stubClassName(o.getReturnType()) + " " +
stubExplicitImplementation(o) + "operator " + o.getName() + "(" + stubParameters(o) + ")" +
stubImplementation(o) + ";\n"
else result = " // Stub generator skipped operator: " + o.getName() + "\n"
}
pragma[noinline]
private string stubEnumConstant(EnumConstant ec, Assembly assembly) {
ec instanceof GeneratedMember and
ec.getALocation() = assembly and
result = " " + escapeIfKeyword(ec.getName()) + " = " + ec.getValue() + ",\n"
}
pragma[noinline]
private string stubProperty(Property p, Assembly assembly) {
p instanceof GeneratedMember and
p.getALocation() = assembly and
result =
" " + stubModifiers(p) + stubClassName(p.getType()) + " " + stubExplicitImplementation(p) +
escapeIfKeyword(p.getName()) + " { " + stubGetter(p) + stubSetter(p) + "}\n"
}
pragma[noinline]
private string stubConstructor(Constructor c, Assembly assembly) {
c instanceof GeneratedMember and
c.getALocation() = assembly and
if c.getDeclaringType() instanceof Enum
then result = ""
else
if
not c.getDeclaringType() instanceof StructExt or
c.getNumberOfParameters() > 0
then
result =
" " + stubModifiers(c) + escapeIfKeyword(c.getName()) + "(" + stubParameters(c) + ")" +
stubConstructorInitializer(c) + " => throw null;\n"
else result = " // Stub generator skipped constructor \n"
}
pragma[noinline]
private string stubIndexer(Indexer i, Assembly assembly) {
i instanceof GeneratedMember and
i.getALocation() = assembly and
result =
" " + stubIndexerNameAttribute(i) + stubModifiers(i) + stubClassName(i.getType()) + " " +
stubExplicitImplementation(i) + "this[" + stubParameters(i) + "] { " + stubGetter(i) +
stubSetter(i) + "}\n"
}
pragma[noinline]
private string stubField(Field f, Assembly assembly) {
f instanceof GeneratedMember and
f.getALocation() = assembly and
not f instanceof EnumConstant and // EnumConstants are already stubbed
exists(string impl |
(if f.isConst() then impl = " = default" else impl = "") and
result =
" " + stubModifiers(f) + stubClassName(f.getType()) + " " + escapeIfKeyword(f.getName()) +
impl + ";\n"
)
}
pragma[noinline]
private string stubEvent(Event e, Assembly assembly) {
e instanceof GeneratedMember and
e.getALocation() = assembly and
result =
" " + stubModifiers(e) + "event " + stubClassName(e.getType()) + " " +
stubExplicitImplementation(e) + escapeIfKeyword(e.getName()) + stubEventAccessors(e) + "\n"
}
pragma[nomagic]
private string stubMember(GeneratedMember m, Assembly assembly) {
result = stubMethod(m, assembly)
or
result = stubOperator(m, assembly)
or
result = stubEnumConstant(m, assembly)
or
result = stubProperty(m, assembly)
or
result = stubConstructor(m, assembly)
or
result = stubIndexer(m, assembly)
or
result = stubField(m, assembly)
or
result = stubEvent(m, assembly)
or
not m instanceof Method and
not m instanceof Operator and
not m instanceof EnumConstant and
not m instanceof Property and
not m instanceof Constructor and
not m instanceof Indexer and
not m instanceof Field and
not m instanceof Event and
m.getALocation() = assembly and
(
result = m.(GeneratedType).getStub(assembly) + "\n"
or
not m instanceof GeneratedType and
result = " // ERR: Stub generator didn't handle member: " + m.getName() + "\n"
)
}
private string stubIndexerNameAttribute(Indexer i) {
if i.getName() != "Item"
then result = "[System.Runtime.CompilerServices.IndexerName(\"" + i.getName() + "\")]\n "
else result = ""
}
private Constructor getBaseConstructor(ValueOrRefType type) {
result =
min(Constructor bc |
type.getBaseClass().getAMember() = bc and
// not the `static` constructor
not bc.isStatic() and
// not a `private` constructor, unless it's `private protected`, or if the derived class is nested
(not bc.isPrivate() or bc.isProtected() or bc.getDeclaringType() = type.getDeclaringType+())
|
bc order by bc.getNumberOfParameters(), stubParameters(bc)
)
}
private string stubConstructorInitializer(Constructor c) {
exists(Constructor baseCtor |
baseCtor = getBaseConstructor(c.getDeclaringType()) and
if baseCtor.getNumberOfParameters() = 0 or c.isStatic()
then result = ""
else result = " : base(" + stubDefaultArguments(baseCtor, c.getDeclaringType()) + ")"
)
or
// abstract base class might not have a constructor
not exists(Constructor baseCtor |
c.getDeclaringType().getBaseClass().getAMember() = baseCtor and not baseCtor.isStatic()
) and
result = ""
}
private string stubExplicit(ConversionOperator op) {
op instanceof ImplicitConversionOperator and result = "implicit "
or
(
op instanceof ExplicitConversionOperator
or
op instanceof CheckedExplicitConversionOperator
) and
result = "explicit "
}
private string stubChecked(Operator o) {
if o instanceof CheckedExplicitConversionOperator then result = "checked " else result = ""
}
private string stubGetter(DeclarationWithGetSetAccessors p) {
if exists(p.getGetter())
then if p.isAbstract() then result = "get; " else result = "get => throw null; "
else result = ""
}
private string stubSetter(DeclarationWithGetSetAccessors p) {
if exists(p.getSetter())
then if p.isAbstract() then result = "set; " else result = "set => throw null; "
else result = ""
}
private string stubSemmleExtractorOptions() {
result =
concat(string s |
exists(CommentLine comment |
s =
"// original-extractor-options:" +
comment.getText().regexpCapture("\\w*semmle-extractor-options:(.*)", 1) + "\n"
)
)
}
/** Gets the generated C# code. */
string generatedCode(Assembly assembly) {
result =
"// This file contains auto-generated code.\n" //
+ "// Generated from `" + assembly.getFullName() + "`.\n" //
+ stubSemmleExtractorOptions() + "\n" //
+ any(GeneratedNamespace ns | ns.isGlobalNamespace()).getStubs(assembly)
}

View File

@@ -1,96 +0,0 @@
# Tool to generate 'stub.cs' files inside C# qltest projects.
# The purpose of this is to stub out assemblies from the qltest case
# so that the test is self-contained and will still run without them.
#
# To do this, it
# 1. Performs a regular qltest to generate a snapshot
# 2. Runs a QL query on the snapshot to find out which symbols are needed,
# then uses QL to generate a string of C# code.
# 3. Re-runs the test to ensure that it still compiles and passes.
import sys
import os
import subprocess
import helpers
print('Script to generate stub.cs files for C# qltest projects')
if len(sys.argv) < 2:
print("Please supply a qltest directory.")
exit(1)
testDir = sys.argv[1]
if not os.path.isdir(testDir):
print("Directory", testDir, "does not exist")
exit(1)
# Does it contain a .ql file and a .cs file?
foundCS = False
foundQL = False
for file in os.listdir(testDir):
if file.endswith(".cs"):
foundCS = True
if file.endswith(".ql") or file.endswith(".qlref"):
foundQL = True
if not foundQL:
print("Test directory does not contain .ql files. Please specify a working qltest directory.")
exit(1)
if not foundCS:
print("Test directory does not contain .cs files. Please specify a working qltest directory.")
exit(1)
csharpQueries = os.path.abspath(os.path.dirname(sys.argv[0]))
outputFile = os.path.join(testDir, 'stubs.cs')
bqrsFile = os.path.join(testDir, 'stubs.bqrs')
print("Stubbing qltest in", testDir)
if os.path.isfile(outputFile):
os.remove(outputFile) # It would interfere with the test.
print("Removed previous", outputFile)
helpers.run_cmd(['codeql', 'test', 'run', '--keep-databases', testDir],
"codeql test failed. Please fix up the test before proceeding.")
dbDir = os.path.join(testDir, os.path.basename(testDir) + ".testproj")
if not os.path.isdir(dbDir):
print("Expected database directory " + dbDir + " not found.")
exit(1)
helpers.run_cmd(['codeql', 'query', 'run', os.path.join(
csharpQueries, 'MinimalStubsFromSource.ql'), '--database', dbDir, '--output', bqrsFile], 'Failed to run the query to generate output file.')
helpers.run_cmd(['codeql', 'bqrs', 'decode', bqrsFile, '--output',
outputFile, '--format=text', '--no-titles'], 'Failed to run the query to generate output file.')
helpers.trim_output_file(outputFile)
if os.path.isfile(bqrsFile):
os.remove(bqrsFile) # Cleanup
print("Removed temp BQRS file", bqrsFile)
cmd = ['codeql', 'test', 'run', testDir]
print('Running ' + ' '.join(cmd))
if subprocess.check_call(cmd):
print('\nTest failed. You may need to fix up', outputFile)
print('It may help to view', outputFile, ' in Visual Studio')
print("Next steps:")
print('1. Look at the compilation errors, and fix up',
outputFile, 'so that the test compiles')
print('2. Re-run codeql test run "' + testDir + '"')
print('3. git add "' + outputFile + '"')
exit(1)
print("\nStub generation successful! Next steps:")
print('1. Edit "semmle-extractor-options" in the .cs files to remove unused references')
print('2. Re-run codeql test run "' + testDir + '"')
print('3. git add "' + outputFile + '"')
print('4. Commit your changes.')
exit(0)

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
Stubs/AllStubsFromSource.ql

View File

@@ -1,249 +0,0 @@
using System;
namespace Test
{
public class Class1
{
public struct Struct1
{
public ValueTuple<int> t1;
public (int, int) t2;
public int i;
public const int j = 42;
public void Method(Struct1 s = new Struct1()) => throw null;
}
public interface Interface1
{
void Method1();
}
internal protected interface Interface2
{
void Method2();
int this[int i] { get; }
}
private protected interface Interface3
{
void Method3();
}
public class Class11 : Interface1, Interface2, Interface3
{
public Class11(int i) => throw null;
public void Method1() => throw null;
void Interface2.Method2() => throw null;
int Interface2.this[int i] => throw null;
void Interface3.Method3() => throw null;
}
public delegate void Delegate1<T>(T i, int j);
public event Delegate1<int> Event1 { add { } remove { } }
public class Class12 : Class11
{
public Class12(int i, float j) : base(1) => throw null;
}
public class GenericType<T>
{
public class X { }
}
public GenericType<int>.X Prop { get; }
public abstract class Class13
{
protected internal virtual void M() => throw null;
public virtual void M1<T>() where T : Class13 => throw null;
public abstract void M2();
}
public abstract class Class14 : Class13
{
protected internal override void M() => throw null;
public override void M1<T>() => throw null;
public abstract override void M2();
}
}
internal class Class2
{
public void M() => throw null;
}
public class Class3
{
public object Item { get; set; }
[System.Runtime.CompilerServices.IndexerName("MyItem")]
public object this[string index] { get { return null; } set { } }
}
public class Class4
{
unsafe public void M(int* p) => throw null;
}
public interface IInterface1
{
void M1() => throw null;
void M2();
}
public class Class5 : IInterface1
{
public void M2() => throw null;
}
public class Class6<T> where T : class, IInterface1
{
public Class6(int i) => throw null;
public virtual void M1<T>() where T : class, IInterface1, new() => throw null;
}
public class Class7 : Class6<Class5>
{
public Class7(int i) : base(i) => throw null;
public override void M1<T>() where T : class => throw null;
}
public class Class8
{
public const int @this = 10;
}
public class Class9
{
private Class9(int i) => throw null;
public class Nested : Class9
{
internal Nested(int i) : base(i) => throw null;
}
public Class9.Nested NestedInstance { get; } = new Class9.Nested(1);
}
public class Class10
{
unsafe public void M1(delegate* unmanaged<System.IntPtr, void> f) => throw null;
}
public interface IInterface2<T> where T : IInterface2<T>
{
static abstract T operator +(T left, T right);
static virtual T operator -(T left, T right) => throw null;
static abstract T operator *(T left, T right);
static virtual T operator /(T left, T right) => throw null;
static abstract explicit operator short(T n);
static abstract explicit operator int(T n);
void M1();
void M2();
}
public interface IInterface3<T> where T : IInterface3<T>
{
static abstract T operator +(T left, T right);
static virtual T operator -(T left, T right) => throw null;
static abstract explicit operator short(T n);
void M1();
}
public class Class11 : IInterface2<Class11>, IInterface3<Class11>
{
public static Class11 operator +(Class11 left, Class11 right) => throw null;
public static Class11 operator -(Class11 left, Class11 right) => throw null;
static Class11 IInterface2<Class11>.operator *(Class11 left, Class11 right) => throw null;
static Class11 IInterface2<Class11>.operator /(Class11 left, Class11 right) => throw null;
public void M1() => throw null;
void IInterface2<Class11>.M2() => throw null;
public static explicit operator short(Class11 n) => 0;
static explicit IInterface2<Class11>.operator int(Class11 n) => 0;
}
public unsafe class MyUnsafeClass
{
public static void M1(delegate*<void> f) => throw null;
public static void M2(int*[] x) => throw null;
public static char* M3() => throw null;
public static void M4(int x) => throw null;
}
public enum Enum1
{
None1,
Some11,
Some12
}
public enum Enum2
{
None2 = 2,
Some21 = 1,
Some22 = 3
}
public enum Enum3
{
Some32,
Some31,
None3
}
public enum Enum4
{
Some41 = 7,
None4 = 2,
Some42 = 6
}
public enum EnumLong : long
{
Some = 223372036854775807,
None = 10
}
}
namespace A1
{
namespace B1
{
}
public class C1 { }
}
namespace A2
{
namespace B2
{
public class C2 { }
}
}
namespace A3
{
public class C3 { }
}
namespace A4
{
namespace B4
{
public class D4 { }
}
public class C4 { }
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
Stubs/MinimalStubsFromSource.ql

View File

@@ -1,123 +0,0 @@
using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using System.Web;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
public class RegexHandler
{
private static readonly string JAVA_CLASS_REGEX = "^(([a-z])+.)+[A-Z]([a-z])+$";
public void ProcessRequest()
{
string userInput = "";
// BAD:
// Artificial regexes
new Regex("^([a-z]+)+$").Match(userInput);
new Regex("^([a-z]*)*$").Replace(userInput, "");
// Known exponential blowup regex for e-mail address validation
// Problematic part is: ([a-zA-Z0-9]+))*
new Regex("^([a-zA-Z0-9])(([\\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$").Match(userInput);
// Known exponential blowup regex for Java class name validation
// Problematic part is: (([a-z])+.)+
new Regex(JAVA_CLASS_REGEX).Match(userInput);
// Static use
Regex.Match(userInput, JAVA_CLASS_REGEX);
// GOOD:
new Regex("^(([a-b]+[c-z]+)+$").Match(userInput);
new Regex("^([a-z]+)+$", RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)).Match(userInput);
Regex.Match(userInput, JAVA_CLASS_REGEX, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
// Known possible FP.
new Regex("^[a-z0-9]+([_.-][a-z0-9]+)*$").Match(userInput);
}
}
// The only purpose of this class is to make sure the extractor extracts the
// relevant library methods
public class LibraryTypeDataFlow
{
void M()
{
int i;
int.Parse("");
int.TryParse("", out i);
bool b;
bool.Parse("");
bool.TryParse("", out b);
Uri uri = null;
uri.ToString();
StringReader sr = new StringReader("");
string s = new string(new[] { 'a' });
string.Join("", "", "", "");
StringBuilder sb = new StringBuilder("");
Lazy<int> l = new Lazy<int>(() => 42);
IEnumerable ie = null;
ie.GetEnumerator();
ie.AsParallel();
ie.AsQueryable();
IEnumerable<int> ieint = null;
ieint.Select(x => x);
List<int> list = null;
list.Find(x => x > 0);
Stack<int> stack = null;
stack.Peek();
ArrayList al = null;
ArrayList.FixedSize(al);
SortedList sl = null;
sl.GetByIndex(0);
Convert.ToInt32("0");
DataContract dc = null;
s = dc.AString;
KeyValuePair<int, string> kvp = new KeyValuePair<int, string>(0, "");
IEnumerator ienum = null;
object o = ienum.Current;
IEnumerator<int> ienumint = null;
i = ienumint.Current;
var task = new Task(() => { });
Task.WhenAll<int>(null, null);
Task.WhenAny<int>(null, null);
Task.Factory.ContinueWhenAll((Task[])null, (Func<Task[], int>)null);
var task2 = new Task<int>(() => 42);
Task<string>.Factory.ContinueWhenAny<int>(new Task<int>[] { task2 }, t => t.Result.ToString());
Encoding.Unicode.GetString(Encoding.Unicode.GetBytes(""));
Path.Combine("", "");
Path.GetDirectoryName("");
Path.GetExtension("");
Path.GetFileName("");
Path.GetFileNameWithoutExtension("");
Path.GetPathRoot("");
HttpContextBase context = null;
string name = context.Request.QueryString["name"];
}
[DataContract]
public class DataContract
{
[DataMember]
public string AString { get; set; }
}
}

View File

@@ -1 +0,0 @@
semmle-extractor-options: /r:System.Text.RegularExpressions.dll /r:System.Collections.Specialized.dll /r:System.Net.dll /r:System.Web.dll /r:System.Net.HttpListener.dll /r:System.Collections.Specialized.dll /r:System.Private.Uri.dll /r:System.Runtime.Extensions.dll /r:System.Linq.Parallel.dll /r:System.Collections.Concurrent.dll /r:System.Linq.Expressions.dll /r:System.Collections.dll /r:System.Linq.Queryable.dll /r:System.Linq.dll /r:System.Collections.NonGeneric.dll /r:System.ObjectModel.dll /r:System.ComponentModel.TypeConverter.dll /r:System.IO.Compression.dll /r:System.IO.Pipes.dll /r:System.Net.Primitives.dll /r:System.Net.Security.dll /r:System.Security.Cryptography.Primitives.dll /r:System.Text.RegularExpressions.dll ${testdir}/../../../resources/stubs/System.Web.cs /r:System.Runtime.Serialization.Primitives.dll

View File

@@ -1,6 +0,0 @@
namespace Test
{
public class Class1
{
}
}

View File

@@ -1 +0,0 @@
| Test.cs:0:0:0:0 | Test.cs |

View File

@@ -1,5 +0,0 @@
import csharp
from File f
where f.fromSource()
select f

View File

@@ -1,3 +0,0 @@
semmle-extractor-options: --load-sources-from-project:../../../resources/stubs/_frameworks/Microsoft.AspNetCore.App/Microsoft.AspNetCore.App.csproj
semmle-extractor-options: /nostdlib
semmle-extractor-options: /noconfig

View File

@@ -5,10 +5,10 @@ Stubs can be generated from Nuget packages with the `make_stubs_nuget.py` script
The following calls generate stubs for `Newtonsoft.Json`:
```
python make_stubs_nuget.py Newtonsoft.Json
python make_stubs_nuget.py Newtonsoft.Json latest
python make_stubs_nuget.py Newtonsoft.Json 13.0.1
python make_stubs_nuget.py Newtonsoft.Json 13.0.1 /Users/tmp/working-dir
python3 make_stubs_nuget.py classlib Newtonsoft.Json
python3 make_stubs_nuget.py classlib Newtonsoft.Json latest
python3 make_stubs_nuget.py classlib Newtonsoft.Json 13.0.1
python3 make_stubs_nuget.py classlib Newtonsoft.Json 13.0.1 /Users/tmp/working-dir
```
The output stubs are found in the `[DIR]/output/stubs` folder and can be copied over to `csharp/ql/test/resources/stubs`.

View File

@@ -82,7 +82,7 @@ print("\n* Creating new global.json file and setting SDK to " + sdk_version)
run_cmd(['dotnet', 'new', 'globaljson', '--force', '--sdk-version', sdk_version, '--output', workDir])
print("\n* Running stub generator")
helpers.run_cmd_cwd(['dotnet', 'run', '--project', thisDir + '/../../../extractor/Semmle.Extraction.CSharp.DependencyStubGenerator/Semmle.Extraction.CSharp.DependencyStubGenerator.csproj'], projectDirIn)
helpers.run_cmd_cwd(['dotnet', 'run', '--project', thisDir + '/../../extractor/Semmle.Extraction.CSharp.DependencyStubGenerator/Semmle.Extraction.CSharp.DependencyStubGenerator.csproj'], projectDirIn)
print("\n* Creating new raw output project")
rawSrcOutputDirName = 'src'