Merge pull request #6960 from erik-krogh/useSetLiteral

use set literal instead of big disjunction of literals
This commit is contained in:
Anders Schack-Mulligen
2021-10-26 14:06:05 +02:00
committed by GitHub
46 changed files with 590 additions and 1259 deletions

View File

@@ -179,15 +179,7 @@ module DOM {
eltName = attr.getElement().getName() and
attrName = attr.getName()
|
(
eltName = "script" or
eltName = "iframe" or
eltName = "embed" or
eltName = "video" or
eltName = "audio" or
eltName = "source" or
eltName = "track"
) and
eltName = ["script", "iframe", "embed", "video", "audio", "source", "track"] and
attrName = "src"
or
(
@@ -258,11 +250,11 @@ module DOM {
/** Gets a call that queries the DOM for a collection of DOM nodes. */
private DataFlow::SourceNode domElementCollection() {
exists(string collectionName |
collectionName = "getElementsByClassName" or
collectionName = "getElementsByName" or
collectionName = "getElementsByTagName" or
collectionName = "getElementsByTagNameNS" or
collectionName = "querySelectorAll"
collectionName =
[
"getElementsByClassName", "getElementsByName", "getElementsByTagName",
"getElementsByTagNameNS", "querySelectorAll"
]
|
(
result = documentRef().getAMethodCall(collectionName) or
@@ -274,11 +266,8 @@ module DOM {
/** Gets a call that creates a DOM node or queries the DOM for a DOM node. */
private DataFlow::SourceNode domElementCreationOrQuery() {
exists(string methodName |
methodName = "createElement" or
methodName = "createElementNS" or
methodName = "createRange" or
methodName = "getElementById" or
methodName = "querySelector"
methodName =
["createElement", "createElementNS", "createRange", "getElementById", "querySelector"]
|
result = documentRef().getAMethodCall(methodName) or
result = DataFlow::globalVarRef(methodName).getACall()
@@ -465,11 +454,7 @@ module DOM {
private class DefaultRange extends Range {
DefaultRange() {
exists(string propName | this = documentRef().getAPropertyRead(propName) |
propName = "documentURI" or
propName = "documentURIObject" or
propName = "location" or
propName = "referrer" or
propName = "URL"
propName = ["documentURI", "documentURIObject", "location", "referrer", "URL"]
)
or
this = DOM::domValueRef().getAPropertyRead("baseUri")

View File

@@ -246,13 +246,7 @@ class File extends Container, @file {
* A file type.
*/
class FileType extends string {
FileType() {
this = "javascript" or
this = "html" or
this = "typescript" or
this = "json" or
this = "yaml"
}
FileType() { this = ["javascript", "html", "typescript", "json", "yaml"] }
/**
* Holds if this is the JavaScript file type.

View File

@@ -291,13 +291,7 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
override predicate isNumbery() {
exists(string name | name = getName() |
name = "number" or
name = "Number" or
name = "double" or
name = "Double" or
name = "int" or
name = "integer" or
name = "Integer"
name = ["number", "Number", "double", "Double", "int", "integer", "Integer"]
)
}

View File

@@ -1405,14 +1405,7 @@ module DataFlow {
*/
class Incompleteness extends string {
Incompleteness() {
this = "await" or
this = "call" or
this = "eval" or
this = "global" or
this = "heap" or
this = "import" or
this = "namespace" or
this = "yield"
this = ["await", "call", "eval", "global", "heap", "import", "namespace", "yield"]
}
}

View File

@@ -29,13 +29,7 @@ newtype TypeTag =
*/
class TypeofTag extends string {
TypeofTag() {
this = "undefined" or
this = "boolean" or
this = "number" or
this = "string" or
this = "function" or
this = "object" or
this = "symbol"
this = ["undefined", "boolean", "number", "string", "function", "object", "symbol"]
}
}

View File

@@ -924,13 +924,11 @@ module TaintTracking {
pred = invoke.getArgument(0) and
succ = invoke
|
name = "Error" or
name = "EvalError" or
name = "RangeError" or
name = "ReferenceError" or
name = "SyntaxError" or
name = "TypeError" or
name = "URIError"
name =
[
"Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError",
"URIError"
]
)
}
}

View File

@@ -408,14 +408,7 @@ private module Forge {
this = mod.getAPropertyRead("cipher").getAMemberCall(createName) and
getArgument(0).asExpr().mayHaveStringValue(cipherName) and
cipherName = cipherPrefix + "-" + cipherSuffix and
(
cipherSuffix = "CBC" or
cipherSuffix = "CFB" or
cipherSuffix = "CTR" or
cipherSuffix = "ECB" or
cipherSuffix = "GCM" or
cipherSuffix = "OFB"
) and
cipherSuffix = ["CBC", "CFB", "CTR", "ECB", "GCM", "OFB"] and
algorithmName = cipherPrefix and
key = getArgument(1)
)

View File

@@ -11,13 +11,7 @@ module EventEmitter {
}
/** Gets the name of a method on `EventEmitter` that registers an event handler. */
string on() {
result = "addListener" or
result = "on" or
result = "once" or
result = "prependListener" or
result = "prependOnceListener"
}
string on() { result = ["addListener", "on", "once", "prependListener", "prependOnceListener"] }
/**
* Gets a node that refers to an EventEmitter object.

View File

@@ -378,23 +378,11 @@ module Express {
*/
private predicate isChainableResponseMethodCall(RouteHandler handler, MethodCallExpr call) {
exists(string name | call.calls(handler.getAResponseExpr(), name) |
name = "append" or
name = "attachment" or
name = "clearCookie" or
name = "contentType" or
name = "cookie" or
name = "format" or
name = "header" or
name = "json" or
name = "jsonp" or
name = "links" or
name = "location" or
name = "send" or
name = "sendStatus" or
name = "set" or
name = "status" or
name = "type" or
name = "vary"
name =
[
"append", "attachment", "location", "send", "sendStatus", "set", "status", "type", "vary",
"clearCookie", "contentType", "cookie", "format", "header", "json", "jsonp", "links"
]
)
}

View File

@@ -429,12 +429,7 @@ private class LibraryAccess extends FileSystemAccess, DataFlow::InvokeNode {
this =
DataFlow::moduleMember("node-dir",
any(string s |
s = "readFiles" or
s = "readFilesStream" or
s = "files" or
s = "promiseFiles" or
s = "subdirs" or
s = "paths"
s = ["readFiles", "readFilesStream", "files", "promiseFiles", "subdirs", "paths"]
)).getACall()
)
or

View File

@@ -18,19 +18,11 @@ abstract class LoggerCall extends DataFlow::CallNode {
* Gets a log level name that is used in RFC5424, `npm`, `console`.
*/
string getAStandardLoggerMethodName() {
result = "crit" or
result = "dir" or
result = "debug" or
result = "error" or
result = "emerg" or
result = "fatal" or
result = "info" or
result = "log" or
result = "notice" or
result = "silly" or
result = "trace" or
result = "verbose" or
result = "warn"
result =
[
"crit", "dir", "trace", "verbose", "warn", "debug", "error", "emerg", "fatal", "info", "log",
"notice", "silly"
]
}
/**

View File

@@ -252,28 +252,13 @@ private module Mongoose {
* Holds if Model method `name` returns a Query.
*/
predicate returnsQuery(string name) {
name = "$where" or
name = "count" or
name = "countDocuments" or
name = "deleteMany" or
name = "deleteOne" or
name = "find" or
name = "findById" or
name = "findByIdAndDelete" or
name = "findByIdAndRemove" or
name = "findByIdAndUpdate" or
name = "findOne" or
name = "findOneAndDelete" or
name = "findOneAndRemove" or
name = "findOneAndReplace" or
name = "findOneAndUpdate" or
name = "geosearch" or
name = "remove" or
name = "replaceOne" or
name = "update" or
name = "updateMany" or
name = "updateOne" or
name = "where"
name =
[
"$where", "count", "findOne", "findOneAndDelete", "findOneAndRemove",
"findOneAndReplace", "findOneAndUpdate", "geosearch", "remove", "replaceOne", "update",
"updateMany", "countDocuments", "updateOne", "where", "deleteMany", "deleteOne", "find",
"findById", "findByIdAndDelete", "findByIdAndRemove", "findByIdAndUpdate"
]
}
/**
@@ -347,117 +332,34 @@ private module Mongoose {
*/
predicate interpretsArgumentAsQuery(string name, int n) {
n = 0 and
(
name = "and" or
name = "count" or
name = "countDocuments" or
name = "deleteMany" or
name = "deleteOne" or
name = "elemMatch" or
name = "find" or
name = "findOne" or
name = "findOneAndDelete" or
name = "findOneAndRemove" or
name = "findOneAndReplace" or
name = "findOneAndUpdate" or
name = "merge" or
name = "nor" or
name = "or" or
name = "remove" or
name = "replaceOne" or
name = "setQuery" or
name = "setUpdate" or
name = "update" or
name = "updateMany" or
name = "updateOne" or
name = "where"
)
name =
[
"and", "count", "findOneAndReplace", "findOneAndUpdate", "merge", "nor", "or", "remove",
"replaceOne", "setQuery", "setUpdate", "update", "countDocuments", "updateMany",
"updateOne", "where", "deleteMany", "deleteOne", "elemMatch", "find", "findOne",
"findOneAndDelete", "findOneAndRemove"
]
or
n = 1 and
(
name = "distinct" or
name = "findOneAndUpdate" or
name = "update" or
name = "updateMany" or
name = "updateOne"
)
name = ["distinct", "findOneAndUpdate", "update", "updateMany", "updateOne"]
}
/**
* Holds if Query method `name` returns a Query.
*/
predicate returnsQuery(string name) {
name = "$where" or
name = "J" or
name = "all" or
name = "and" or
name = "batchsize" or
name = "box" or
name = "center" or
name = "centerSphere" or
name = "circle" or
name = "collation" or
name = "comment" or
name = "count" or
name = "countDocuments" or
name = "distinct" or
name = "elemMatch" or
name = "equals" or
name = "error" or
name = "estimatedDocumentCount" or
name = "exists" or
name = "explain" or
name = "find" or
name = "findById" or
name = "findOne" or
name = "findOneAndRemove" or
name = "findOneAndUpdate" or
name = "geometry" or
name = "get" or
name = "gt" or
name = "gte" or
name = "hint" or
name = "in" or
name = "intersects" or
name = "lean" or
name = "limit" or
name = "lt" or
name = "lte" or
name = "map" or
name = "map" or
name = "maxDistance" or
name = "maxTimeMS" or
name = "maxscan" or
name = "mod" or
name = "ne" or
name = "near" or
name = "nearSphere" or
name = "nin" or
name = "or" or
name = "orFail" or
name = "polygon" or
name = "populate" or
name = "read" or
name = "readConcern" or
name = "regexp" or
name = "remove" or
name = "select" or
name = "session" or
name = "set" or
name = "setOptions" or
name = "setQuery" or
name = "setUpdate" or
name = "size" or
name = "skip" or
name = "slaveOk" or
name = "slice" or
name = "snapshot" or
name = "sort" or
name = "update" or
name = "w" or
name = "where" or
name = "within" or
name = "wtimeout"
name =
[
"$where", "J", "comment", "count", "countDocuments", "distinct", "elemMatch", "equals",
"error", "estimatedDocumentCount", "exists", "explain", "all", "find", "findById",
"findOne", "findOneAndRemove", "findOneAndUpdate", "geometry", "get", "gt", "gte",
"hint", "and", "in", "intersects", "lean", "limit", "lt", "lte", "map", "map",
"maxDistance", "maxTimeMS", "batchsize", "maxscan", "mod", "ne", "near", "nearSphere",
"nin", "or", "orFail", "polygon", "populate", "box", "read", "readConcern", "regexp",
"remove", "select", "session", "set", "setOptions", "setQuery", "setUpdate", "center",
"size", "skip", "slaveOk", "slice", "snapshot", "sort", "update", "w", "where",
"within", "centerSphere", "wtimeout", "circle", "collation"
]
}
/**

View File

@@ -523,14 +523,11 @@ module NodeJSLib {
/** A write to the file system. */
private class NodeJSFileSystemAccessWrite extends FileSystemWriteAccess, NodeJSFileSystemAccess {
NodeJSFileSystemAccessWrite() {
methodName = "appendFile" or
methodName = "appendFileSync" or
methodName = "write" or
methodName = "writeFile" or
methodName = "writeFileSync" or
methodName = "writeSync" or
methodName = "link" or
methodName = "linkSync"
methodName =
[
"appendFile", "appendFileSync", "write", "writeFile", "writeFileSync", "writeSync",
"link", "linkSync"
]
}
override DataFlow::Node getADataNode() {
@@ -699,13 +696,7 @@ module NodeJSLib {
)
or
shell = false and
(
methodName = "execFile" or
methodName = "execFileSync" or
methodName = "spawn" or
methodName = "spawnSync" or
methodName = "fork"
)
methodName = ["execFile", "execFileSync", "spawn", "spawnSync", "fork"]
) and
// all of the above methods take the command as their first argument
result = getParameter(0).getARhs()
@@ -716,13 +707,7 @@ module NodeJSLib {
override predicate isShellInterpreted(DataFlow::Node arg) { arg = getACommandArgument(true) }
override DataFlow::Node getArgumentList() {
(
methodName = "execFile" or
methodName = "execFileSync" or
methodName = "fork" or
methodName = "spawn" or
methodName = "spawnSync"
) and
methodName = ["execFile", "execFileSync", "fork", "spawn", "spawnSync"] and
// all of the above methods take the argument list as their second argument
result = getParameter(1).getARhs()
}

View File

@@ -11,17 +11,11 @@ module PkgCloud {
private predicate takesConfigurationObject(DataFlow::InvokeNode invk, int i) {
exists(DataFlow::ModuleImportNode mod, DataFlow::SourceNode receiver, string type |
mod.getPath() = "pkgcloud" and
(
type = "compute" or
type = "storage" or
type = "database" or
type = "dns" or
type = "blockstorage" or
type = "loadbalancer" or
type = "network" or
type = "orchestration" or
type = "cdn"
) and
type =
[
"compute", "storage", "database", "dns", "blockstorage", "loadbalancer", "network",
"orchestration", "cdn"
] and
(
// require('pkgcloud').compute
receiver = mod.getAPropertyRead(type)

View File

@@ -80,17 +80,7 @@ module ShellJS {
*/
private class ShellJSGenericFileAccess extends FileSystemAccess, ShellJSCall {
ShellJSGenericFileAccess() {
name = "cd" or
name = "cp" or
name = "chmod" or
name = "pushd" or
name = "find" or
name = "ls" or
name = "ln" or
name = "mkdir" or
name = "mv" or
name = "rm" or
name = "touch"
name = ["cd", "cp", "touch", "chmod", "pushd", "find", "ls", "ln", "mkdir", "mv", "rm"]
}
override DataFlow::Node getAPathArgument() { result = getAnArgument() }
@@ -110,13 +100,7 @@ module ShellJS {
* A file system access that returns the contents of a file.
*/
private class ShellJSRead extends FileSystemReadAccess, ShellJSCall {
ShellJSRead() {
name = "cat" or
name = "head" or
name = "sort" or
name = "tail" or
name = "uniq"
}
ShellJSRead() { name = ["cat", "head", "sort", "tail", "uniq"] }
override DataFlow::Node getAPathArgument() { result = getAnArgument() }

View File

@@ -34,14 +34,7 @@ private class LibraryFormatter extends PrintfStyleCall {
returns = false and
mod = "console" and
(
(
meth = "debug" or
meth = "error" or
meth = "info" or
meth = "log" or
meth = "trace" or
meth = "warn"
) and
meth = ["debug", "error", "info", "log", "trace", "warn"] and
formatIndex = 0
or
meth = "assert" and formatIndex = 1

View File

@@ -9,26 +9,11 @@ module Templating {
* Gets a string that is a known template delimiter.
*/
string getADelimiter() {
result = "<%" or
result = "%>" or
result = "{{" or
result = "}}" or
result = "{%" or
result = "%}" or
result = "<@" or
result = "@>" or
result = "<#" or
result = "#>" or
result = "{#" or
result = "#}" or
result = "{$" or
result = "$}" or
result = "[%" or
result = "%]" or
result = "[[" or
result = "]]" or
result = "<?" or
result = "?>"
result =
[
"<%", "%>", "{#", "#}", "{$", "$}", "[%", "%]", "[[", "]]", "<?", "?>", "{{", "}}", "{%",
"%}", "<@", "@>", "<#", "#>"
]
}
/**

View File

@@ -441,19 +441,11 @@ module JQuery {
* arguments as HTML.
*/
predicate isMethodArgumentInterpretedAsHtml(string name) {
name = "after" or
name = "append" or
name = "appendTo" or
name = "before" or
name = "html" or
name = "insertAfter" or
name = "insertBefore" or
name = "prepend" or
name = "prependTo" or
name = "replaceWith" or
name = "wrap" or
name = "wrapAll" or
name = "wrapInner"
name =
[
"after", "append", "wrap", "wrapAll", "wrapInner", "appendTo", "before", "html",
"insertAfter", "insertBefore", "prepend", "prependTo", "replaceWith"
]
}
/**
@@ -461,13 +453,7 @@ module JQuery {
* arguments as a selector.
*/
predicate isMethodArgumentInterpretedAsSelector(string name) {
name = "appendTo" or
name = "insertAfter" or
name = "insertBefore" or
name = "prependTo" or
name = "wrap" or
name = "wrapAll" or
name = "wrapInner"
name = ["appendTo", "insertAfter", "insertBefore", "prependTo", "wrap", "wrapAll", "wrapInner"]
}
module DollarSource {

View File

@@ -153,90 +153,36 @@ class JSLintOptions extends JSLintDirective {
private string jsLintImplicitGlobal(string category) {
// cf. http://www.jslint.com/help.html#global
category = "browser" and
(
result = "clearInterval" or
result = "clearTimeout" or
result = "document" or
result = "event" or
result = "frames" or
result = "history" or
result = "Image" or
result = "location" or
result = "name" or
result = "navigator" or
result = "Option" or
result = "parent" or
result = "screen" or
result = "setInterval" or
result = "setTimeout" or
result = "window" or
result = "XMLHttpRequest"
)
result =
[
"clearInterval", "clearTimeout", "Option", "parent", "screen", "setInterval", "setTimeout",
"window", "XMLHttpRequest", "document", "event", "frames", "history", "Image", "location",
"name", "navigator"
]
or
category = "devel" and
(
result = "alert" or
result = "confirm" or
result = "console" or
result = "Debug" or
result = "opera" or
result = "prompt" or
result = "WSH"
)
result = ["alert", "confirm", "console", "Debug", "opera", "prompt", "WSH"]
or
category = "node" and
(
result = "Buffer" or
result = "clearInterval" or
result = "clearTimeout" or
result = "console" or
result = "exports" or
result = "result" or
result = "module" or
result = "process" or
result = "querystring" or
result = "require" or
result = "setInterval" or
result = "setTimeout" or
result = "__filename" or
result = "__dirname"
)
result =
[
"Buffer", "clearInterval", "setInterval", "setTimeout", "__filename", "__dirname",
"clearTimeout", "console", "exports", "result", "module", "process", "querystring", "require"
]
or
category = "couch" and
(
result = "emit" or
result = "getRow" or
result = "isArray" or
result = "log" or
result = "provides" or
result = "registerType" or
result = "require" or
result = "send" or
result = "start" or
result = "sum" or
result = "toJSON"
)
result =
[
"emit", "getRow", "toJSON", "isArray", "log", "provides", "registerType", "require", "send",
"start", "sum"
]
or
category = "rhino" and
(
result = "defineClass" or
result = "deserialize" or
result = "gc" or
result = "help" or
result = "load" or
result = "loadClass" or
result = "print" or
result = "quit" or
result = "readFile" or
result = "readUrl" or
result = "runCommand" or
result = "seal" or
result = "serialize" or
result = "spawn" or
result = "sync" or
result = "toint32" or
result = "version"
)
result =
[
"defineClass", "deserialize", "runCommand", "seal", "serialize", "spawn", "sync", "toint32",
"version", "gc", "help", "load", "loadClass", "print", "quit", "readFile", "readUrl"
]
}
/**

View File

@@ -88,14 +88,7 @@ class DomMethodCallExpr extends MethodCallExpr {
name = "setAttributeNS" and argPos = 2
) and
// restrict to potentially dangerous attributes
exists(string attr |
attr = "action" or
attr = "formaction" or
attr = "href" or
attr = "src" or
attr = "xlink:href" or
attr = "data"
|
exists(string attr | attr = ["action", "formaction", "href", "src", "xlink:href", "data"] |
getArgument(argPos - 1).getStringValue().toLowerCase() = attr
)
)

View File

@@ -751,13 +751,11 @@ module TaintedPath {
exists(mcn.getAnArgument().asExpr().getIntValue())
or
exists(string argumentlessMethodName |
argumentlessMethodName = "toLocaleLowerCase" or
argumentlessMethodName = "toLocaleUpperCase" or
argumentlessMethodName = "toLowerCase" or
argumentlessMethodName = "toUpperCase" or
argumentlessMethodName = "trim" or
argumentlessMethodName = "trimLeft" or
argumentlessMethodName = "trimRight"
argumentlessMethodName =
[
"toLocaleLowerCase", "toLocaleUpperCase", "toLowerCase", "toUpperCase", "trim",
"trimLeft", "trimRight"
]
|
name = argumentlessMethodName
)

View File

@@ -39,11 +39,7 @@ module TypeConfusionThroughParameterTampering {
private class StringArrayAmbiguousMethodCall extends Sink {
StringArrayAmbiguousMethodCall() {
exists(string name, DataFlow::MethodCallNode mc |
name = "concat" or
name = "includes" or
name = "indexOf" or
name = "lastIndexOf" or
name = "slice"
name = ["concat", "includes", "indexOf", "lastIndexOf", "slice"]
|
mc.calls(this, name) and
// ignore patterns that are innocent in practice

View File

@@ -67,14 +67,7 @@ module PolynomialReDoS {
|
this = mcn.getArgument(0) and
regexp = mcn.getReceiver() and
(
name = "match" or
name = "split" or
name = "matchAll" or
name = "replace" or
name = "replaceAll" or
name = "search"
)
name = ["match", "split", "matchAll", "replace", "replaceAll", "search"]
or
this = mcn.getReceiver() and
regexp = mcn.getArgument(0) and

View File

@@ -71,13 +71,7 @@ predicate isCompatibleRequestedService(InjectableFunctionServiceRequest request,
isRunMethod(request) or
isControllerFunction(request)
) and
(
kind = "value" or
kind = "service" or
kind = "factory" or
kind = "constant" or
kind = "provider-value"
)
kind = ["value", "service", "factory", "constant", "provider-value"]
or
isControllerFunction(request) and
kind = "controller-only"

View File

@@ -76,13 +76,11 @@ class StateUpdateVolatileMethod extends Function {
// - componentsWillMount
// - componentsDidMount
exists(ReactComponent c |
methodName = "componentDidUnmount" or
methodName = "componentDidUpdate" or
methodName = "componentWillUpdate" or
methodName = "getDefaultProps" or
methodName = "getInitialState" or
methodName = "render" or
methodName = "shouldComponentUpdate"
methodName =
[
"componentDidUnmount", "componentDidUpdate", "componentWillUpdate", "getDefaultProps",
"getInitialState", "render", "shouldComponentUpdate"
]
|
this = c.getInstanceMethod(methodName)
)

View File

@@ -79,14 +79,11 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
or
// flow through string methods
exists(DataFlow::MethodCallNode mc, string m |
m = "replace" or
m = "replaceAll" or
m = "slice" or
m = "substr" or
m = "substring" or
m = "toLowerCase" or
m = "toUpperCase" or
m = "trim"
m =
[
"replace", "replaceAll", "slice", "substr", "substring", "toLowerCase", "toUpperCase",
"trim"
]
|
mc = nd and m = mc.getMethodName() and allBackslashesEscaped(mc.getReceiver())
)

View File

@@ -4,15 +4,11 @@ import semmle.javascript.security.SensitiveActions
query predicate cleartextPasswordExpr(CleartextPasswordExpr e) { any() }
string getASamplePassword() {
result = "abcdefgh" or
result = "sOKY6ccizpmvF*32so%Q" or
result = "XXXXXXXX" or
result = "example_password" or
result = "change_me" or
result = "" or
result = "insert-auth-from-gui" or
result = "admin" or
result = "root"
result =
[
"abcdefgh", "sOKY6ccizpmvF*32so%Q", "XXXXXXXX", "example_password", "change_me", "",
"insert-auth-from-gui", "admin", "root"
]
}
query predicate dummyPasswords(string password, boolean isDummy) {