Merge pull request #5567 from asgerf/js/sql-models

Approved by esbena
This commit is contained in:
CodeQL CI
2021-04-09 07:11:10 -07:00
committed by GitHub
17 changed files with 277 additions and 59 deletions

View File

@@ -28,30 +28,52 @@ module SQL {
* Provides classes modelling the (API compatible) `mysql` and `mysql2` packages.
*/
private module MySql {
private string moduleName() { result = ["mysql", "mysql2", "mysql2/promise"] }
/** Gets the package name `mysql` or `mysql2`. */
API::Node mysql() { result = API::moduleImport(["mysql", "mysql2"]) }
API::Node mysql() { result = API::moduleImport(moduleName()) }
/** Gets a reference to `mysql.createConnection`. */
API::Node createConnection() { result = mysql().getMember("createConnection") }
API::Node createConnection() {
result = mysql().getMember(["createConnection", "createConnectionPromise"])
}
/** Gets a reference to `mysql.createPool`. */
API::Node createPool() { result = mysql().getMember("createPool") }
API::Node createPool() { result = mysql().getMember(["createPool", "createPoolCluster"]) }
/** Gets a node that contains a MySQL pool created using `mysql.createPool()`. */
API::Node pool() { result = createPool().getReturn() }
API::Node pool() {
result = createPool().getReturn()
or
result = pool().getMember("on").getReturn()
or
result = API::Node::ofType(moduleName(), ["Pool", "PoolCluster"])
}
/** Gets a data flow node that contains a freshly created MySQL connection instance. */
API::Node connection() {
result = createConnection().getReturn()
or
result = createConnection().getReturn().getPromised()
or
result = pool().getMember("getConnection").getParameter(0).getParameter(1)
or
result = pool().getMember("getConnection").getPromised()
or
exists(API::CallNode call |
call = pool().getMember("on").getACall() and
call.getArgument(0).getStringValue() = ["connection", "acquire", "release"] and
result = call.getParameter(1).getParameter(0)
)
or
result = API::Node::ofType(moduleName(), ["Connection", "PoolConnection"])
}
/** A call to the MySql `query` method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() {
exists(API::Node recv | recv = pool() or recv = connection() |
this = recv.getMember("query").getACall()
this = recv.getMember(["query", "execute"]).getACall()
)
}
@@ -112,7 +134,20 @@ private module Postgres {
// pool.connect(function(err, client) { ... })
result = pool().getMember("connect").getParameter(0).getParameter(1)
or
// await pool.connect()
result = pool().getMember("connect").getReturn().getPromised()
or
result = pgpConnection().getMember("client")
or
exists(API::CallNode call |
call = pool().getMember("on").getACall() and
call.getArgument(0).getStringValue() = ["connect", "acquire"] and
result = call.getParameter(1).getParameter(0)
)
or
result = client().getMember("on").getReturn()
or
result = API::Node::ofType("pg", ["Client", "PoolClient"])
}
/** Gets a constructor that when invoked constructs a new connection pool. */
@@ -129,6 +164,10 @@ private module Postgres {
result = newPool().getInstance()
or
result = pgpDatabase().getMember("$pool")
or
result = pool().getMember("on").getReturn()
or
result = API::Node::ofType("pg", "Pool")
}
/** A call to the Postgres `query` method. */
@@ -140,7 +179,11 @@ private module Postgres {
/** An expression that is passed to the `query` method and hence interpreted as SQL. */
class QueryString extends SQL::SqlString {
QueryString() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
QueryString() {
this = any(QueryCall qc).getAQueryArgument().asExpr()
or
this = API::moduleImport("pg-cursor").getParameter(0).getARhs().asExpr()
}
}
/** An expression that is passed as user name or password when creating a client or a pool. */
@@ -299,24 +342,17 @@ private module Sqlite {
}
/** Gets an expression that constructs a Sqlite database instance. */
API::Node newDb() {
API::Node database() {
// new require('sqlite3').Database()
result = sqlite().getMember("Database").getInstance()
or
result = API::Node::ofType("sqlite3", "Database")
}
/** A call to a Sqlite query method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() {
exists(string meth |
meth = "all" or
meth = "each" or
meth = "exec" or
meth = "get" or
meth = "prepare" or
meth = "run"
|
this = newDb().getMember(meth).getACall()
)
this = database().getMember(["all", "each", "exec", "get", "prepare", "run"]).getACall()
}
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
@@ -335,15 +371,32 @@ private module MsSql {
/** Gets a reference to the `mssql` module. */
API::Node mssql() { result = API::moduleImport("mssql") }
/** Gets an expression that creates a request object. */
API::Node request() {
// new require('mssql').Request()
result = mssql().getMember("Request").getInstance()
/** Gets a node referring to an instance of the given class. */
API::Node mssqlClass(string name) {
result = mssql().getMember(name).getInstance()
or
// request.input(...)
result = request().getMember("input").getReturn()
result = API::Node::ofType("mssql", name)
}
/** Gets an API node referring to a Request object. */
API::Node request() {
result = mssqlClass("Request")
or
result = request().getMember(["input", "replaceInput", "output", "replaceOutput"]).getReturn()
or
result = [transaction(), pool()].getMember("request").getReturn()
}
/** Gets an API node referring to a Transaction object. */
API::Node transaction() {
result = mssqlClass("Transaction")
or
result = pool().getMember("transaction").getReturn()
}
/** Gets a API node referring to a ConnectionPool object. */
API::Node pool() { result = mssqlClass("ConnectionPool") }
/** A tagged template evaluated as a query. */
private class QueryTemplateExpr extends DatabaseAccess, DataFlow::ValueNode {
override TaggedTemplateExpr astNode;
@@ -359,7 +412,7 @@ private module MsSql {
/** A call to a MsSql query method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() { this = request().getMember(["query", "batch"]).getACall() }
QueryCall() { this = [mssql(), request()].getMember(["query", "batch"]).getACall() }
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
}
@@ -410,22 +463,34 @@ private module MsSql {
* Provides classes modelling the `sequelize` package.
*/
private module Sequelize {
/** Gets an import of the `sequelize` module. */
API::Node sequelize() { result = API::moduleImport("sequelize") }
/** Gets an import of the `sequelize` module or one that re-exports it. */
API::Node sequelize() { result = API::moduleImport(["sequelize", "sequelize-typescript"]) }
/** Gets an expression that creates an instance of the `Sequelize` class. */
API::Node newSequelize() { result = sequelize().getInstance() }
API::Node instance() {
result = [sequelize(), sequelize().getMember("Sequelize")].getInstance()
or
result = API::Node::ofType(["sequelize", "sequelize-typescript"], ["Sequelize", "default"])
}
/** A call to `Sequelize.query`. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() { this = newSequelize().getMember("query").getACall() }
QueryCall() { this = instance().getMember("query").getACall() }
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
override DataFlow::Node getAQueryArgument() {
result = getArgument(0)
or
result = getOptionArgument(0, "query")
}
}
/** An expression that is passed to `Sequelize.query` method and hence interpreted as SQL. */
class QueryString extends SQL::SqlString {
QueryString() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
QueryString() {
this = any(QueryCall qc).getAQueryArgument().asExpr()
or
this = sequelize().getMember(["literal", "asIs"]).getParameter(0).getARhs().asExpr()
}
}
/**
@@ -478,6 +543,8 @@ private module Spanner {
API::Node database() {
result =
spanner().getReturn().getMember("instance").getReturn().getMember("database").getReturn()
or
result = API::Node::ofType("@google-cloud/spanner", "Database")
}
/**
@@ -485,61 +552,81 @@ private module Spanner {
*/
API::Node v1SpannerClient() {
result = spanner().getMember("v1").getMember("SpannerClient").getInstance()
or
result = API::Node::ofType("@google-cloud/spanner", "v1.SpannerClient")
}
/**
* Gets a node that refers to a transaction object.
*/
API::Node transaction() {
result = database().getMember("runTransaction").getParameter(0).getParameter(1)
result =
database()
.getMember(["runTransaction", "runTransactionAsync"])
.getParameter([0, 1])
.getParameter(1)
or
result = API::Node::ofType("@google-cloud/spanner", "Transaction")
}
/** Gets an API node referring to a `BatchTransaction` object. */
API::Node batchTransaction() {
result = database().getMember("batchTransaction").getReturn()
or
result = database().getMember("createBatchTransaction").getReturn().getPromised()
or
result = API::Node::ofType("@google-cloud/spanner", "BatchTransaction")
}
/**
* A call to a Spanner method that executes a SQL query.
*/
abstract class SqlExecution extends DatabaseAccess, DataFlow::InvokeNode {
/**
* Gets the position of the query argument; default is zero, which can be overridden
* by subclasses.
*/
int getQueryArgumentPosition() { result = 0 }
abstract class SqlExecution extends DatabaseAccess, DataFlow::InvokeNode { }
/**
* A SQL execution that takes the input directly in the first argument or in the `sql` option.
*/
class SqlExecutionDirect extends SqlExecution {
SqlExecutionDirect() {
this = database().getMember(["run", "runPartitionedUpdate", "runStream"]).getACall()
or
this = transaction().getMember(["run", "runStream", "runUpdate"]).getACall()
or
this = batchTransaction().getMember("createQueryPartitions").getACall()
}
override DataFlow::Node getAQueryArgument() {
result = getArgument(getQueryArgumentPosition()) or
result = getOptionArgument(getQueryArgumentPosition(), "sql")
result = getArgument(0)
or
result = getOptionArgument(0, "sql")
}
}
/**
* A call to `Database.run`, `Database.runPartitionedUpdate` or `Database.runStream`.
* A SQL execution that takes an array of SQL strings or { sql: string } objects.
*/
class DatabaseRunCall extends SqlExecution {
DatabaseRunCall() {
this = database().getMember(["run", "runPartitionedUpdate", "runStream"]).getACall()
class SqlExecutionBatch extends SqlExecution, API::CallNode {
SqlExecutionBatch() { this = transaction().getMember("batchUpdate").getACall() }
override DataFlow::Node getAQueryArgument() {
// just use the whole array as the query argument, as arrays becomes tainted if one of the elements
// are tainted
result = getArgument(0)
or
result = getParameter(0).getUnknownMember().getMember("sql").getARhs()
}
}
/**
* A call to `Transaction.run`, `Transaction.runStream` or `Transaction.runUpdate`.
* A SQL execution that only takes the input in the `sql` option, and do not accept query strings
* directly.
*/
class TransactionRunCall extends SqlExecution {
TransactionRunCall() {
this = transaction().getMember(["run", "runStream", "runUpdate"]).getACall()
}
}
/**
* A call to `v1.SpannerClient.executeSql` or `v1.SpannerClient.executeStreamingSql`.
*/
class ExecuteSqlCall extends SqlExecution {
ExecuteSqlCall() {
class SqlExecutionWithOption extends SqlExecution {
SqlExecutionWithOption() {
this = v1SpannerClient().getMember(["executeSql", "executeStreamingSql"]).getACall()
}
override DataFlow::Node getAQueryArgument() {
// `executeSql` and `executeStreamingSql` do not accept query strings directly
result = getOptionArgument(0, "sql")
}
override DataFlow::Node getAQueryArgument() { result = getOptionArgument(0, "sql") }
}
/**

View File

@@ -6,6 +6,7 @@
| mysql1.js:7:14:7:21 | 'secret' | password |
| mysql1a.js:10:9:10:12 | 'me' | user name |
| mysql1a.js:11:13:11:20 | 'secret' | password |
| mysql2-promise.js:8:9:8:14 | 'root' | user name |
| mysql2.js:7:21:7:25 | 'bob' | user name |
| mysql2.js:8:21:8:28 | 'secret' | password |
| mysql2tst.js:8:9:8:14 | 'root' | user name |

View File

@@ -1,28 +1,47 @@
| mssql1.js:7:40:7:72 | select ... e id = |
| mssql1.js:7:75:7:79 | value |
| mssql1.js:10:19:10:30 | 'SELECT 123' |
| mssql2.js:5:15:5:34 | 'select 1 as number' |
| mssql2.js:13:15:13:66 | 'create ... table' |
| mssql2.js:22:24:22:43 | 'select 1 as number' |
| mssql2.js:29:30:29:81 | 'create ... table' |
| mssql-types.ts:7:31:7:42 | 'SELECT 123' |
| mysql1.js:13:18:13:43 | 'SELECT ... lution' |
| mysql1.js:18:18:22:1 | {\\n s ... vid']\\n} |
| mysql1a.js:17:18:17:43 | 'SELECT ... lution' |
| mysql2-promise.js:14:3:14:62 | 'SELECT ... ` > 45' |
| mysql2-promise.js:23:3:23:56 | 'SELECT ... e` > ?' |
| mysql2-promise.js:31:19:31:39 | 'SELECT ... users' |
| mysql2-promise.js:32:21:32:41 | 'SELECT ... users' |
| mysql2-types.ts:4:16:4:36 | 'SELECT ... users' |
| mysql2.js:12:12:12:37 | 'SELECT ... lution' |
| mysql2tst.js:14:3:14:62 | 'SELECT ... ` > 45' |
| mysql2tst.js:23:3:23:56 | 'SELECT ... e` > ?' |
| mysql2tst.js:31:19:31:39 | 'SELECT ... users' |
| mysql2tst.js:32:21:32:41 | 'SELECT ... users' |
| mysql3.js:14:20:14:52 | 'SELECT ... etable' |
| mysql3.js:26:14:26:31 | 'SELECT something' |
| mysql4.js:14:18:14:20 | sql |
| mysqlImport.js:3:18:5:1 | {\\n s ... = ?',\\n} |
| postgres1.js:37:21:37:24 | text |
| postgres2.js:30:16:30:41 | 'SELECT ... number' |
| postgres2.js:43:15:43:26 | 'SELECT 123' |
| postgres2.js:46:15:46:47 | new Cur ... users') |
| postgres2.js:46:26:46:46 | 'SELECT ... users' |
| postgres3.js:15:16:15:40 | 'SELECT ... s name' |
| postgres5.js:8:21:8:25 | query |
| postgres-types.ts:4:18:4:29 | 'SELECT 123' |
| postgresImport.js:4:18:4:43 | 'SELECT ... number' |
| sequelize2.js:10:17:10:118 | 'SELECT ... Y name' |
| sequelize2.js:12:17:15:1 | {\\n que ... [123]\\n} |
| sequelize2.js:13:10:13:20 | 'SELECT $1' |
| sequelize2.js:17:31:17:41 | '123 + 345' |
| sequelize-types.ts:7:24:7:35 | 'SELECT 123' |
| sequelize.js:8:17:8:118 | 'SELECT ... Y name' |
| sequelizeImport.js:3:17:3:118 | 'SELECT ... Y name' |
| spanner2.js:5:26:5:35 | "SQL code" |
| spanner2.js:7:35:7:44 | "SQL code" |
| spanner-types.ts:4:12:4:23 | 'SELECT 123' |
| spanner.js:6:8:6:17 | "SQL code" |
| spanner.js:7:8:7:26 | { sql: "SQL code" } |
| spanner.js:7:15:7:24 | "SQL code" |
@@ -41,7 +60,11 @@
| spanner.js:18:16:18:25 | "SQL code" |
| spanner.js:19:16:19:34 | { sql: "SQL code" } |
| spanner.js:19:23:19:32 | "SQL code" |
| spanner.js:23:12:23:23 | 'SELECT 123' |
| spanner.js:26:12:26:38 | 'UPDATE ... = @baz' |
| spanner.js:31:18:31:24 | queries |
| spannerImport.js:4:8:4:17 | "SQL code" |
| sqlite-types.ts:4:12:4:49 | "UPDATE ... id = ?" |
| sqlite.js:7:8:7:45 | "UPDATE ... id = ?" |
| sqliteArray.js:6:12:6:49 | "UPDATE ... id = ?" |
| sqliteImport.js:2:8:2:44 | "UPDATE ... id = ?" |

View File

@@ -0,0 +1,9 @@
import { ConnectionPool } from "mssql";
class Foo {
constructor(private pool: ConnectionPool) {}
doSomething() {
this.pool.request().query('SELECT 123');
}
}

View File

@@ -6,6 +6,8 @@ async () => {
const pool = await sql.connect('mssql://username:password@localhost/database')
const result = await sql.query`select * from mytable where id = ${value}`
console.dir(result)
sql.query('SELECT 123');
} catch (err) {
// ... error checks
}

View File

@@ -0,0 +1,32 @@
// Adapted from the documentation of https://github.com/sidorares/node-mysql2,
// which is licensed under the MIT license; see file node-mysql2-License.
const mysql = require('mysql2/promise');
// create the connection to database
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
database: 'test'
});
// simple query
connection.query(
'SELECT * FROM `table` WHERE `name` = "Page" AND `age` > 45',
function(err, results, fields) {
console.log(results); // results contains rows returned by server
console.log(fields); // fields contains extra meta data about results, if available
}
);
// with placeholder
connection.query(
'SELECT * FROM `table` WHERE `name` = ? AND `age` > ?',
['Page', 45],
function(err, results) {
console.log(results);
}
);
const conn2 = await mysql.createConnectionPromise();
await conn2.query('SELECT * FROM users');
await conn2.execute('SELECT * FROM users');

View File

@@ -0,0 +1,5 @@
import { Connection } from "mysql2";
export function doSomething(conn: Connection) {
conn.query('SELECT * FROM users');
}

View File

@@ -26,3 +26,7 @@ connection.query(
console.log(results);
}
);
const conn2 = await mysql.createConnectionPromise();
await conn2.query('SELECT * FROM users');
await conn2.execute('SELECT * FROM users');

View File

@@ -21,3 +21,7 @@ pool.getConnection(function(err, connection) {
// Don't use the connection here, it has been returned to the pool.
});
});
pool.on('connection', conn => {
conn.query('SELECT something');
});

View File

@@ -0,0 +1,5 @@
import { Client } from "pg";
function submitSomething(client: Client) {
client.query('SELECT 123');
}

View File

@@ -38,3 +38,9 @@ pool.connect(function(err, client, done) {
//output: 1
});
});
let client2 = await pool.connect();
client2.query('SELECT 123');
const Cursor = require('pg-cursor');
client2.query(new Cursor('SELECT * from users'));

View File

@@ -0,0 +1,9 @@
import Sequelize from 'sequelize';
export class Foo {
constructor(private seq: Sequelize) {}
method() {
this.seq.query('SELECT 123');
}
}

View File

@@ -9,3 +9,9 @@ const sequelize = new Sequelize('database', {
});
sequelize.query('SELECT * FROM Products WHERE (name LIKE \'%' + criteria + '%\') AND deletedAt IS NULL) ORDER BY name');
sequelize.query({
query: 'SELECT $1',
values: [123]
});
let value = Sequelize.literal('123 + 345');

View File

@@ -0,0 +1,5 @@
import { Database } from "@google-cloud/spanner";
export function doSomething(db: Database) {
db.run('SELECT 123');
}

View File

@@ -17,6 +17,18 @@ db.runTransaction((err, tx) => {
tx.runStream({ sql: "SQL code" });
tx.runUpdate("SQL code");
tx.runUpdate({ sql: "SQL code" });
const queries = [
{
sql: 'SELECT 123',
},
{
sql: 'UPDATE foo SET bar = @baz',
params: {key: 'baz', value: '123'}
}
];
tx.batchUpdate(queries, () => {});
});
exports.instance = instance;

View File

@@ -0,0 +1,5 @@
import { Database } from "sqlite3";
export function doSomething(db: Database) {
db.run("UPDATE tbl SET name = ? WHERE id = ?", "bar", 2);
}