mirror of
https://github.com/github/codeql.git
synced 2025-12-18 01:33:15 +01:00
Merge branch 'master' into python-objectapi-to-valueapi-signatureoverriddenmethod
This commit is contained in:
@@ -1,62 +1,65 @@
|
||||
# Contributing to CodeQL
|
||||
|
||||
We welcome contributions to our standard library and standard checks. Got an idea for a new check, or how to improve an existing query? Then please go ahead and open a pull request!
|
||||
|
||||
Before we accept your pull request, we require that you have agreed to our Contributor License Agreement, this is not something that you need to do before you submit your pull request, but until you've done so, we will be unable to accept your contribution.
|
||||
|
||||
## Adding a new query
|
||||
|
||||
If you have an idea for a query that you would like to share with other Semmle users, please open a pull request to add it to this repository.
|
||||
Follow the steps below to help other users understand what your query does, and to ensure that your query is consistent with the other Semmle queries.
|
||||
|
||||
1. **Consult the documentation for query writers**
|
||||
We welcome contributions to our CodeQL libraries and queries. Got an idea for a new check, or how to improve an existing query? Then please go ahead and open a pull request!
|
||||
|
||||
There is lots of useful documentation to help you write queries, ranging from information about query file structure to tutorials for specific target languages. For more information on the documentation available, see [Writing CodeQL queries](https://help.semmle.com/QL/learn-ql/writing-queries/writing-queries.html) on [help.semmle.com](https://help.semmle.com).
|
||||
|
||||
2. **Format your code correctly**
|
||||
|
||||
All of Semmle's standard queries and libraries are uniformly formatted for clarity and consistency, so we strongly recommend that all contributions follow the same formatting guidelines. If you use CodeQL for VS Code, you can autoformat your query in the [Editor](https://help.semmle.com/codeql/codeql-for-vscode/reference/editor.html#autoformatting). For more information, see the [CodeQL style guide](https://github.com/Semmle/ql/blob/master/docs/ql-style-guide.md).
|
||||
## Submitting a new experimental query
|
||||
|
||||
3. **Make sure your query has the correct metadata**
|
||||
If you have an idea for a query that you would like to share with other CodeQL users, please open a pull request to add it to this repository. New queries start out in a `<language>/ql/src/experimental` directory, to which they can be merged when they meet the following requirements.
|
||||
|
||||
Query metadata is used by Semmle's analysis to identify your query and make sure the query results are displayed properly.
|
||||
The most important metadata to include are the `@name`, `@description`, and the `@kind`.
|
||||
Other metadata properties (`@precision`, `@severity`, and `@tags`) are usually added after the query has been reviewed by Semmle staff.
|
||||
For more information on writing query metadata, see the [Query metadata style guide](https://github.com/Semmle/ql/blob/master/docs/query-metadata-style-guide.md).
|
||||
1. **Directory structure**
|
||||
|
||||
4. **Make sure the `select` statement is compatible with the query type**
|
||||
There are five language-specific query directories in this repository:
|
||||
|
||||
The `select` statement of your query must be compatible with the query type (determined by the `@kind` metadata property) for alert or path results to be displayed correctly in LGTM and CodeQL for VS Code.
|
||||
For more information on `select` statement format, see [Introduction to query files](https://help.semmle.com/QL/learn-ql/writing-queries/introduction-to-queries.html#select-clause) on help.semmle.com.
|
||||
* C/C++: `cpp/ql/src`
|
||||
* C#: `csharp/ql/src`
|
||||
* Java: `java/ql/src`
|
||||
* JavaScript: `javascript/ql/src`
|
||||
* Python: `python/ql/src`
|
||||
|
||||
5. **Save your query in a `.ql` file in the correct language directory in this repository**
|
||||
Each language-specific directory contains further subdirectories that group queries based on their `@tags` or purpose.
|
||||
- Experimental queries and libraries are stored in the `experimental` subdirectory within each language-specific directory in the [CodeQL repository](https://github.com/Semmle/ql). For example, experimental Java queries and libraries are stored in `java/ql/src/experimental` and any corresponding tests in `java/ql/test/experimental`.
|
||||
- The structure of an `experimental` subdirectory mirrors the structure of its parent directory.
|
||||
- Select or create an appropriate directory in `experimental` based on the existing directory structure of `experimental` or its parent directory.
|
||||
|
||||
There are five language-specific directories in this repository:
|
||||
2. **Query metadata**
|
||||
|
||||
* C/C++: `ql/cpp/ql/src`
|
||||
* C#: `ql/csharp/ql/src`
|
||||
* Java: `ql/java/ql/src`
|
||||
* JavaScript: `ql/javascript/ql/src`
|
||||
* Python: `ql/python/ql/src`
|
||||
- The query `@id` must conform to all the requirements in the [guide on query metadata](docs/query-metadata-style-guide.md#query-id-id). In particular, it must not clash with any other queries in the repository, and it must start with the appropriate language-specific prefix.
|
||||
- The query must have a `@name` and `@description` to explain its purpose.
|
||||
- The query must have a `@kind` and `@problem.severity` as required by CodeQL tools.
|
||||
|
||||
Each language-specific directory contains further subdirectories that group queries based on their `@tags` properties or purpose. Select the appropriate subdirectory for your new query, or create a new one if necessary.
|
||||
For details, see the [guide on query metadata](docs/query-metadata-style-guide.md).
|
||||
|
||||
6. **Write a query help file**
|
||||
Make sure the `select` statement is compatible with the query `@kind`. See [Introduction to query files](https://help.semmle.com/QL/learn-ql/writing-queries/introduction-to-queries.html#select-clause) on help.semmle.com.
|
||||
|
||||
Query help files explain the purpose of your query to other users. Write your query help in a `.qhelp` file and save it in the same directory as your new query.
|
||||
For more information on writing query help, see the [Query help style guide](https://github.com/Semmle/ql/blob/master/docs/query-help-style-guide.md).
|
||||
3. **Formatting**
|
||||
|
||||
7. **Maintain backwards compatibility**
|
||||
- The queries and libraries must be [autoformatted](https://help.semmle.com/codeql/codeql-for-vscode/reference/editor.html#autoformatting).
|
||||
|
||||
The standard CodeQL libraries must evolve in a backwards compatible manner. If any backwards incompatible changes need to be made, the existing API must first be marked as deprecated. This is done by adding a `deprecated` annotation along with a QLDoc reference to the replacement API. Only after at least one full release cycle has elapsed may the old API be removed.
|
||||
4. **Compilation**
|
||||
|
||||
In addition to contributions to our standard queries and libraries, we also welcome contributions of a more experimental nature, which do not need to fulfill all the requirements listed above. See the guidelines for [experimental queries and libraries](docs/experimental.md) for details.
|
||||
- Compilation of the query and any associated libraries and tests must be resilient to future development of the [supported](docs/supported-queries.md) libraries. This means that the functionality cannot use internal libraries, cannot depend on the output of `getAQlClass`, and cannot make use of regexp matching on `toString`.
|
||||
- The query and any associated libraries and tests must not cause any compiler warnings to be emitted (such as use of deprecated functionality or missing `override` annotations).
|
||||
|
||||
5. **Results**
|
||||
|
||||
- The query must have at least one true positive result on some revision of a real project.
|
||||
|
||||
6. **Contributor License Agreement**
|
||||
|
||||
- The contributor can satisfy the [CLA](#contributor-license-agreement).
|
||||
|
||||
Experimental queries and libraries may not be actively maintained as the [supported](docs/supported-queries.md) libraries evolve. They may also be changed in backwards-incompatible ways or may be removed entirely in the future without deprecation warnings.
|
||||
|
||||
After the experimental query is merged, we welcome pull requests to improve it. Before a query can be moved out of the `experimental` subdirectory, it must satisfy [the requirements for being a supported query](docs/supported-queries.md).
|
||||
|
||||
## Using your personal data
|
||||
|
||||
If you contribute to this project, we will record your name and email
|
||||
address (as provided by you with your contributions) as part of the code
|
||||
repositories, which might be made public. We might also use this information
|
||||
repositories, which are public. We might also use this information
|
||||
to contact you in relation to your contributions, as well as in the
|
||||
normal course of software development. We also store records of your
|
||||
CLA agreements. Under GDPR legislation, we do this
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# CodeQL
|
||||
|
||||
This open source repository contains the standard CodeQL libraries and queries that power [LGTM](https://lgtm.com), and the other products that [Semmle](https://semmle.com) makes available to its customers worldwide.
|
||||
This open source repository contains the standard CodeQL libraries and queries that power [LGTM](https://lgtm.com) and the other CodeQL products that [GitHub](https://github.com) makes available to its customers worldwide.
|
||||
|
||||
## How do I learn CodeQL and run queries?
|
||||
|
||||
@@ -13,4 +13,4 @@ We welcome contributions to our standard library and standard checks. Do you hav
|
||||
|
||||
## License
|
||||
|
||||
The code in this repository is licensed under [Apache License 2.0](LICENSE) by [Semmle](https://semmle.com).
|
||||
The code in this repository is licensed under [Apache License 2.0](LICENSE) by [GitHub](https://github.com).
|
||||
|
||||
@@ -18,12 +18,14 @@ The following changes in version 1.24 affect C/C++ analysis in all applications.
|
||||
| No space for zero terminator (`cpp/no-space-for-terminator`) | More true positive results | This query now identifies a wider variety of buffer allocations using the `semmle.code.cpp.models.interfaces.Allocation` library. |
|
||||
| Memory is never freed (`cpp/memory-never-freed`) | More true positive results | This query now identifies a wider variety of buffer allocations using the `semmle.code.cpp.models.interfaces.Allocation` library. |
|
||||
| Memory may not be freed (`cpp/memory-may-not-be-freed`) | More true positive results | This query now identifies a wider variety of buffer allocations using the `semmle.code.cpp.models.interfaces.Allocation` library. |
|
||||
| Mismatching new/free or malloc/delete (`cpp/new-free-mismatch`) | Fewer false positive results | Fixed false positive results in template code. |
|
||||
| Missing return statement (`cpp/missing-return`) | Fewer false positive results | Functions containing `asm` statements are no longer highlighted by this query. |
|
||||
| No space for zero terminator (`cpp/no-space-for-terminator`) | More correct results | String arguments to formatting functions are now (usually) expected to be null terminated strings. |
|
||||
| Hard-coded Japanese era start date (`cpp/japanese-era/exact-era-date`) | | This query is no longer run on LGTM. |
|
||||
| No space for zero terminator (`cpp/no-space-for-terminator`) | Fewer false positive results | This query has been modified to be more conservative when identifying which pointers point to null-terminated strings. This approach produces fewer, more accurate results. |
|
||||
| Overloaded assignment does not return 'this' (`cpp/assignment-does-not-return-this`) | Fewer false positive results | This query no longer reports incorrect results in template classes. |
|
||||
| Unsafe array for days of the year (`cpp/leap-year/unsafe-array-for-days-of-the-year`) | | This query is no longer run on LGTM. |
|
||||
| Unsigned comparison to zero (`cpp/unsigned-comparison-zero`) | More correct results | This query now also looks for comparisons of the form `0 <= x`. |
|
||||
|
||||
## Changes to libraries
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
|
||||
* Support for the following frameworks and libraries has been improved:
|
||||
- [Electron](https://electronjs.org/)
|
||||
- [fstream](https://www.npmjs.com/package/fstream)
|
||||
- [Handlebars](https://www.npmjs.com/package/handlebars)
|
||||
- [jsonfile](https://www.npmjs.com/package/jsonfile)
|
||||
- [Koa](https://www.npmjs.com/package/koa)
|
||||
- [Node.js](https://nodejs.org/)
|
||||
- [Socket.IO](https://socket.io/)
|
||||
@@ -32,11 +34,20 @@
|
||||
- [for-in](https://www.npmjs.com/package/for-in)
|
||||
- [for-own](https://www.npmjs.com/package/for-own)
|
||||
- [http2](https://nodejs.org/api/http2.html)
|
||||
- [jQuery](https://jquery.com/)
|
||||
- [lazy-cache](https://www.npmjs.com/package/lazy-cache)
|
||||
- [mongodb](https://www.npmjs.com/package/mongodb)
|
||||
- [ncp](https://www.npmjs.com/package/ncp)
|
||||
- [node-dir](https://www.npmjs.com/package/node-dir)
|
||||
- [path-exists](https://www.npmjs.com/package/path-exists)
|
||||
- [react](https://www.npmjs.com/package/react)
|
||||
- [recursive-readdir](https://www.npmjs.com/package/recursive-readdir)
|
||||
- [request](https://www.npmjs.com/package/request)
|
||||
- [rimraf](https://www.npmjs.com/package/rimraf)
|
||||
- [send](https://www.npmjs.com/package/send)
|
||||
- [typeahead.js](https://www.npmjs.com/package/typeahead.js)
|
||||
- [vinyl-fs](https://www.npmjs.com/package/vinyl-fs)
|
||||
- [write-file-atomic](https://www.npmjs.com/package/write-file-atomic)
|
||||
- [ws](https://github.com/websockets/ws)
|
||||
|
||||
## New queries
|
||||
@@ -67,6 +78,8 @@
|
||||
| Uncontrolled command line (`js/command-line-injection`) | More results | This query now recognizes additional ways of constructing arguments to `cmd.exe` and `/bin/sh`. |
|
||||
| Syntax error (`js/syntax-error`) | Lower severity | This results of this query are now displayed with lower severity. |
|
||||
| Use of password hash with insufficient computational effort (`js/insufficient-password-hash`) | Fewer false positive results | This query now recognizes additional cases that do not require secure hashing. |
|
||||
| Useless regular-expression character escape (`js/useless-regexp-character-escape`) | Fewer false positive results | This query now distinguishes escapes in strings and regular expression literals. |
|
||||
| Identical operands (`js/redundant-operation`) | Fewer results | This query now recognizes cases where the operands change a value using ++/-- expressions. |
|
||||
|
||||
## Changes to libraries
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import semmle.code.cpp.dataflow.DataFlow
|
||||
*/
|
||||
predicate allocExpr(Expr alloc, string kind) {
|
||||
isAllocationExpr(alloc) and
|
||||
not alloc.isFromUninstantiatedTemplate(_) and
|
||||
(
|
||||
alloc instanceof FunctionCall and
|
||||
kind = "malloc"
|
||||
|
||||
@@ -19,15 +19,19 @@ class ConstantZero extends Expr {
|
||||
* Holds if `candidate` is an expression such that if it's unsigned then we
|
||||
* want an alert at `ge`.
|
||||
*/
|
||||
private predicate lookForUnsignedAt(GEExpr ge, Expr candidate) {
|
||||
// Base case: `candidate >= 0`
|
||||
ge.getRightOperand() instanceof ConstantZero and
|
||||
candidate = ge.getLeftOperand().getFullyConverted() and
|
||||
// left operand was a signed or unsigned IntegralType before conversions
|
||||
private predicate lookForUnsignedAt(RelationalOperation ge, Expr candidate) {
|
||||
// Base case: `candidate >= 0` (or `0 <= candidate`)
|
||||
(
|
||||
ge instanceof GEExpr or
|
||||
ge instanceof LEExpr
|
||||
) and
|
||||
ge.getLesserOperand() instanceof ConstantZero and
|
||||
candidate = ge.getGreaterOperand().getFullyConverted() and
|
||||
// left/greater operand was a signed or unsigned IntegralType before conversions
|
||||
// (not a pointer, checking a pointer >= 0 is an entirely different mistake)
|
||||
// (not an enum, as the fully converted type of an enum is compiler dependent
|
||||
// so checking an enum >= 0 is always reasonable)
|
||||
ge.getLeftOperand().getUnderlyingType() instanceof IntegralType
|
||||
ge.getGreaterOperand().getUnderlyingType() instanceof IntegralType
|
||||
or
|
||||
// Recursive case: `...(largerType)candidate >= 0`
|
||||
exists(Conversion conversion |
|
||||
@@ -37,7 +41,7 @@ private predicate lookForUnsignedAt(GEExpr ge, Expr candidate) {
|
||||
)
|
||||
}
|
||||
|
||||
class UnsignedGEZero extends GEExpr {
|
||||
class UnsignedGEZero extends ComparisonOperation {
|
||||
UnsignedGEZero() {
|
||||
exists(Expr ue |
|
||||
lookForUnsignedAt(this, ue) and
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#include <stdlib.h>
|
||||
#include <sys/param.h>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
|
||||
void callSetuidAndCheck(int uid) {
|
||||
if (setuid(uid) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void callSetgidAndCheck(int gid) {
|
||||
if (setgid(gid) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Correct ways to drop priv.
|
||||
|
||||
void correctDropPrivInline() {
|
||||
if (setgroups(0, NULL)) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (setgid(-2) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (setuid(-2) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void correctDropPrivInScope() {
|
||||
{
|
||||
if (setgroups(0, NULL)) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (setgid(-2) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (setuid(-2) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void correctOrderForInitgroups() {
|
||||
struct passwd *pw = getpwuid(0);
|
||||
if (pw) {
|
||||
if (initgroups(pw->pw_name, -2)) {
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
// Unhandled.
|
||||
}
|
||||
int rc = setuid(-2);
|
||||
if (rc) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void correctDropPrivInScopeParent() {
|
||||
{
|
||||
callSetgidAndCheck(-2);
|
||||
}
|
||||
correctOrderForInitgroups();
|
||||
}
|
||||
|
||||
void incorrectNoReturnCodeCheck() {
|
||||
int user = -2;
|
||||
if (user) {
|
||||
if (user) {
|
||||
int rc = setgid(user);
|
||||
(void)rc;
|
||||
initgroups("nobody", user);
|
||||
}
|
||||
if (user) {
|
||||
setuid(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void correctDropPrivInFunctionCall() {
|
||||
if (setgroups(0, NULL)) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
callSetgidAndCheck(-2);
|
||||
callSetuidAndCheck(-2);
|
||||
}
|
||||
|
||||
/// Incorrect, out of order gid and uid.
|
||||
/// Calling uid before gid will fail.
|
||||
|
||||
void incorrectDropPrivOutOfOrderInline() {
|
||||
if (setuid(-2) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (setgid(-2) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void incorrectDropPrivOutOfOrderInScope() {
|
||||
{
|
||||
if (setuid(-2) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
setgid(-2);
|
||||
}
|
||||
|
||||
void incorrectDropPrivOutOfOrderWithFunction() {
|
||||
callSetuidAndCheck(-2);
|
||||
|
||||
if (setgid(-2) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void incorrectDropPrivOutOfOrderWithFunction2() {
|
||||
callSetuidAndCheck(-2);
|
||||
callSetgidAndCheck(-2);
|
||||
}
|
||||
|
||||
void incorrectDropPrivNoCheck() {
|
||||
setgid(-2);
|
||||
setuid(-2);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>The code attempts to drop privilege in an incorrect order by
|
||||
erroneous dropping user privilege before groups. This has security
|
||||
impact if the return codes are not checked.</p>
|
||||
|
||||
<p>False positives include code performing negative checks, making
|
||||
sure that setgid or setgroups does not work, meaning permissions are
|
||||
dropped. Additionally, other forms of sandboxing may be present removing
|
||||
any residual risk, for example a dedicated user namespace.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Set the new group ID, then set the target user's intended groups by
|
||||
dropping previous supplemental source groups and initializing target
|
||||
groups, and finally set the target user.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>The following example demonstrates out of order calls.</p>
|
||||
<sample src="PrivilegeDroppingOutoforder.c" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>CERT C Coding Standard:
|
||||
<a href="https://wiki.sei.cmu.edu/confluence/display/c/POS37-C.+Ensure+that+privilege+relinquishment+is+successful">POS37-C. Ensure that privilege relinquishment is successful</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @name LinuxPrivilegeDroppingOutoforder
|
||||
* @description A syscall commonly associated with privilege dropping is being called out of order.
|
||||
* Normally a process drops group ID and sets supplimental groups for the target user
|
||||
* before setting the target user ID. This can have security impact if the return code
|
||||
* from these methods is not checked.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id cpp/drop-linux-privileges-outoforder
|
||||
* @tags security
|
||||
* external/cwe/cwe-273
|
||||
* @precision medium
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
predicate argumentMayBeRoot(Expr e) {
|
||||
e.getValue() = "0" or
|
||||
e.(VariableAccess).getTarget().getName().toLowerCase().matches("%root%")
|
||||
}
|
||||
|
||||
class SetuidLikeFunctionCall extends FunctionCall {
|
||||
SetuidLikeFunctionCall() {
|
||||
(getTarget().hasGlobalName("setuid") or getTarget().hasGlobalName("setresuid")) and
|
||||
// setuid/setresuid with the root user are false positives.
|
||||
not argumentMayBeRoot(getArgument(0))
|
||||
}
|
||||
}
|
||||
|
||||
class SetuidLikeWrapperCall extends FunctionCall {
|
||||
SetuidLikeFunctionCall baseCall;
|
||||
|
||||
SetuidLikeWrapperCall() {
|
||||
this = baseCall
|
||||
or
|
||||
exists(SetuidLikeWrapperCall fc |
|
||||
this.getTarget() = fc.getEnclosingFunction() and
|
||||
baseCall = fc.getBaseCall()
|
||||
)
|
||||
}
|
||||
|
||||
SetuidLikeFunctionCall getBaseCall() { result = baseCall }
|
||||
}
|
||||
|
||||
class CallBeforeSetuidFunctionCall extends FunctionCall {
|
||||
CallBeforeSetuidFunctionCall() {
|
||||
(
|
||||
getTarget().hasGlobalName("setgid") or
|
||||
getTarget().hasGlobalName("setresgid") or
|
||||
// Compatibility may require skipping initgroups and setgroups return checks.
|
||||
// A stricter best practice is to check the result and errnor for EPERM.
|
||||
getTarget().hasGlobalName("initgroups") or
|
||||
getTarget().hasGlobalName("setgroups")
|
||||
) and
|
||||
// setgid/setresgid/etc with the root group are false positives.
|
||||
not argumentMayBeRoot(getArgument(0))
|
||||
}
|
||||
}
|
||||
|
||||
class CallBeforeSetuidWrapperCall extends FunctionCall {
|
||||
CallBeforeSetuidFunctionCall baseCall;
|
||||
|
||||
CallBeforeSetuidWrapperCall() {
|
||||
this = baseCall
|
||||
or
|
||||
exists(CallBeforeSetuidWrapperCall fc |
|
||||
this.getTarget() = fc.getEnclosingFunction() and
|
||||
baseCall = fc.getBaseCall()
|
||||
)
|
||||
}
|
||||
|
||||
CallBeforeSetuidFunctionCall getBaseCall() { result = baseCall }
|
||||
}
|
||||
|
||||
predicate setuidBeforeSetgid(
|
||||
SetuidLikeWrapperCall setuidWrapper, CallBeforeSetuidWrapperCall setgidWrapper
|
||||
) {
|
||||
setgidWrapper.getAPredecessor+() = setuidWrapper
|
||||
}
|
||||
|
||||
predicate isAccessed(FunctionCall fc) {
|
||||
exists(Variable v | v.getAnAssignedValue() = fc)
|
||||
or
|
||||
exists(Operation c | fc = c.getAChild() | c.isCondition())
|
||||
or
|
||||
// ignore pattern where result is intentionally ignored by a cast to void.
|
||||
fc.hasExplicitConversion()
|
||||
}
|
||||
|
||||
from Function func, CallBeforeSetuidFunctionCall fc, SetuidLikeFunctionCall setuid
|
||||
where
|
||||
setuidBeforeSetgid(setuid, fc) and
|
||||
// Require the call return code to be used in a condition or assigned.
|
||||
// This introduces false negatives where the return is checked but then
|
||||
// errno == EPERM allows execution to continue.
|
||||
not isAccessed(fc) and
|
||||
func = fc.getEnclosingFunction()
|
||||
select fc,
|
||||
"This function is called within " + func + ", and potentially after " +
|
||||
"$@, and may not succeed. Be sure to check the return code and errno, otherwise permissions " +
|
||||
"may not be dropped.", setuid, setuid.getTarget().getName()
|
||||
@@ -366,6 +366,49 @@ class LocalVariable extends LocalScopeVariable, @localvariable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A variable whose contents always have static storage duration. This can be a
|
||||
* global variable, a namespace variable, a static local variable, or a static
|
||||
* member variable.
|
||||
*/
|
||||
class StaticStorageDurationVariable extends Variable {
|
||||
StaticStorageDurationVariable() {
|
||||
this instanceof GlobalOrNamespaceVariable
|
||||
or
|
||||
this.(LocalVariable).isStatic()
|
||||
or
|
||||
this.(MemberVariable).isStatic()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the initializer for this variable is evaluated at compile time.
|
||||
*/
|
||||
predicate hasConstantInitialization() {
|
||||
not runtimeExprInStaticInitializer(this.getInitializer().getExpr())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is an expression in a static initializer that must be evaluated
|
||||
* at run time. This predicate computes "is non-const" instead of "is const"
|
||||
* since computing "is const" for an aggregate literal with many children would
|
||||
* either involve recursion through `forall` on those children or an iteration
|
||||
* through the rank numbers of the children, both of which can be slow.
|
||||
*/
|
||||
private predicate runtimeExprInStaticInitializer(Expr e) {
|
||||
inStaticInitializer(e) and
|
||||
if e instanceof AggregateLiteral
|
||||
then runtimeExprInStaticInitializer(e.getAChild())
|
||||
else not e.getFullyConverted().isConstant()
|
||||
}
|
||||
|
||||
/** Holds if `e` is part of the initializer of a `StaticStorageDurationVariable`. */
|
||||
private predicate inStaticInitializer(Expr e) {
|
||||
exists(StaticStorageDurationVariable var | e = var.getInitializer().getExpr())
|
||||
or
|
||||
inStaticInitializer(e.getParent())
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ variable which has global scope or namespace scope. For example the
|
||||
* variables `a` and `b` in the following code:
|
||||
|
||||
@@ -443,8 +443,7 @@ private Node getControlOrderChildSparse(Node n, int i) {
|
||||
private predicate skipInitializer(Initializer init) {
|
||||
exists(LocalVariable local |
|
||||
init = local.getInitializer() and
|
||||
local.isStatic() and
|
||||
not runtimeExprInStaticInitializer(init.getExpr())
|
||||
local.(StaticStorageDurationVariable).hasConstantInitialization()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class UnaryMinusExpr extends UnaryArithmeticOperation, @arithnegexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "UnaryMinusExpr" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,7 +30,7 @@ class UnaryPlusExpr extends UnaryArithmeticOperation, @unaryplusexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "UnaryPlusExpr" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +109,7 @@ class PrefixIncrExpr extends IncrementOperation, PrefixCrementOperation, @preinc
|
||||
|
||||
override string getCanonicalQLClass() { result = "PrefixIncrExpr" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +125,7 @@ class PrefixDecrExpr extends DecrementOperation, PrefixCrementOperation, @predec
|
||||
|
||||
override string getCanonicalQLClass() { result = "PrefixDecrExpr" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +141,7 @@ class PostfixIncrExpr extends IncrementOperation, PostfixCrementOperation, @post
|
||||
|
||||
override string getCanonicalQLClass() { result = "PostfixIncrExpr" }
|
||||
|
||||
override int getPrecedence() { result = 16 }
|
||||
override int getPrecedence() { result = 17 }
|
||||
|
||||
override string toString() { result = "... " + getOperator() }
|
||||
}
|
||||
@@ -159,7 +159,7 @@ class PostfixDecrExpr extends DecrementOperation, PostfixCrementOperation, @post
|
||||
|
||||
override string getCanonicalQLClass() { result = "PostfixDecrExpr" }
|
||||
|
||||
override int getPrecedence() { result = 16 }
|
||||
override int getPrecedence() { result = 17 }
|
||||
|
||||
override string toString() { result = "... " + getOperator() }
|
||||
}
|
||||
@@ -210,7 +210,7 @@ class AddExpr extends BinaryArithmeticOperation, @addexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "AddExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,7 +224,7 @@ class SubExpr extends BinaryArithmeticOperation, @subexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "SubExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,7 +238,7 @@ class MulExpr extends BinaryArithmeticOperation, @mulexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "MulExpr" }
|
||||
|
||||
override int getPrecedence() { result = 13 }
|
||||
override int getPrecedence() { result = 14 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,7 +252,7 @@ class DivExpr extends BinaryArithmeticOperation, @divexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "DivExpr" }
|
||||
|
||||
override int getPrecedence() { result = 13 }
|
||||
override int getPrecedence() { result = 14 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +266,7 @@ class RemExpr extends BinaryArithmeticOperation, @remexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "RemExpr" }
|
||||
|
||||
override int getPrecedence() { result = 13 }
|
||||
override int getPrecedence() { result = 14 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,7 +283,7 @@ class ImaginaryMulExpr extends BinaryArithmeticOperation, @jmulexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "ImaginaryMulExpr" }
|
||||
|
||||
override int getPrecedence() { result = 13 }
|
||||
override int getPrecedence() { result = 14 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,7 +300,7 @@ class ImaginaryDivExpr extends BinaryArithmeticOperation, @jdivexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "ImaginaryDivExpr" }
|
||||
|
||||
override int getPrecedence() { result = 13 }
|
||||
override int getPrecedence() { result = 14 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,7 +318,7 @@ class RealImaginaryAddExpr extends BinaryArithmeticOperation, @fjaddexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "RealImaginaryAddExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,7 +336,7 @@ class ImaginaryRealAddExpr extends BinaryArithmeticOperation, @jfaddexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "ImaginaryRealAddExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,7 +354,7 @@ class RealImaginarySubExpr extends BinaryArithmeticOperation, @fjsubexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "RealImaginarySubExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,7 +372,7 @@ class ImaginaryRealSubExpr extends BinaryArithmeticOperation, @jfsubexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "ImaginaryRealSubExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,7 +416,7 @@ class PointerAddExpr extends PointerArithmeticOperation, @paddexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "PointerAddExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -431,7 +431,7 @@ class PointerSubExpr extends PointerArithmeticOperation, @psubexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "PointerSubExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -446,5 +446,5 @@ class PointerDiffExpr extends PointerArithmeticOperation, @pdiffexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "PointerDiffExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class UnaryBitwiseOperation extends UnaryOperation, @un_bitwise_op_expr { }
|
||||
class ComplementExpr extends UnaryBitwiseOperation, @complementexpr {
|
||||
override string getOperator() { result = "~" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
|
||||
override string getCanonicalQLClass() { result = "ComplementExpr" }
|
||||
}
|
||||
@@ -33,7 +33,7 @@ class BinaryBitwiseOperation extends BinaryOperation, @bin_bitwise_op_expr { }
|
||||
class LShiftExpr extends BinaryBitwiseOperation, @lshiftexpr {
|
||||
override string getOperator() { result = "<<" }
|
||||
|
||||
override int getPrecedence() { result = 11 }
|
||||
override int getPrecedence() { result = 12 }
|
||||
|
||||
override string getCanonicalQLClass() { result = "LShiftExpr" }
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class LShiftExpr extends BinaryBitwiseOperation, @lshiftexpr {
|
||||
class RShiftExpr extends BinaryBitwiseOperation, @rshiftexpr {
|
||||
override string getOperator() { result = ">>" }
|
||||
|
||||
override int getPrecedence() { result = 11 }
|
||||
override int getPrecedence() { result = 12 }
|
||||
|
||||
override string getCanonicalQLClass() { result = "RShiftExpr" }
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ abstract class Call extends Expr, NameQualifiableElement {
|
||||
*/
|
||||
abstract Function getTarget();
|
||||
|
||||
override int getPrecedence() { result = 16 }
|
||||
override int getPrecedence() { result = 17 }
|
||||
|
||||
override string toString() { none() }
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class CStyleCast extends Cast, @c_style_cast {
|
||||
|
||||
override string getCanonicalQLClass() { result = "CStyleCast" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +103,7 @@ class StaticCast extends Cast, @static_cast {
|
||||
|
||||
override string getCanonicalQLClass() { result = "StaticCast" }
|
||||
|
||||
override int getPrecedence() { result = 16 }
|
||||
override int getPrecedence() { result = 17 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +121,7 @@ class ConstCast extends Cast, @const_cast {
|
||||
|
||||
override string getCanonicalQLClass() { result = "ConstCast" }
|
||||
|
||||
override int getPrecedence() { result = 16 }
|
||||
override int getPrecedence() { result = 17 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,7 +139,7 @@ class ReinterpretCast extends Cast, @reinterpret_cast {
|
||||
|
||||
override string getCanonicalQLClass() { result = "ReinterpretCast" }
|
||||
|
||||
override int getPrecedence() { result = 16 }
|
||||
override int getPrecedence() { result = 17 }
|
||||
}
|
||||
|
||||
private predicate isArithmeticOrEnum(Type type) {
|
||||
@@ -608,7 +608,7 @@ class PrvalueAdjustmentConversion extends Cast {
|
||||
class DynamicCast extends Cast, @dynamic_cast {
|
||||
override string toString() { result = "dynamic_cast<" + this.getType().getName() + ">..." }
|
||||
|
||||
override int getPrecedence() { result = 16 }
|
||||
override int getPrecedence() { result = 17 }
|
||||
|
||||
override string getCanonicalQLClass() { result = "DynamicCast" }
|
||||
|
||||
@@ -631,7 +631,7 @@ class UuidofOperator extends Expr, @uuidof {
|
||||
else result = "__uuidof(0)"
|
||||
}
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
|
||||
/** Gets the contained type. */
|
||||
Type getTypeOperand() { uuidof_bind(underlyingElement(this), unresolveElement(result)) }
|
||||
@@ -669,7 +669,7 @@ class TypeidOperator extends Expr, @type_id {
|
||||
|
||||
override string toString() { result = "typeid ..." }
|
||||
|
||||
override int getPrecedence() { result = 16 }
|
||||
override int getPrecedence() { result = 17 }
|
||||
|
||||
override predicate mayBeImpure() { this.getExpr().mayBeImpure() }
|
||||
|
||||
@@ -700,7 +700,7 @@ class SizeofPackOperator extends Expr, @sizeof_pack {
|
||||
* A C/C++ sizeof expression.
|
||||
*/
|
||||
abstract class SizeofOperator extends Expr, @runtime_sizeof {
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -763,7 +763,7 @@ class SizeofTypeOperator extends SizeofOperator {
|
||||
* A C++11 `alignof` expression.
|
||||
*/
|
||||
abstract class AlignofOperator extends Expr, @runtime_alignof {
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -642,7 +642,7 @@ class AddressOfExpr extends UnaryOperation, @address_of {
|
||||
|
||||
override string getOperator() { result = "&" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
|
||||
override predicate mayBeImpure() { this.getOperand().mayBeImpure() }
|
||||
|
||||
@@ -664,7 +664,7 @@ class ReferenceToExpr extends Conversion, @reference_to {
|
||||
|
||||
override string getCanonicalQLClass() { result = "ReferenceToExpr" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -687,7 +687,7 @@ class PointerDereferenceExpr extends UnaryOperation, @indirect {
|
||||
|
||||
override string getOperator() { result = "*" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
|
||||
override predicate mayBeImpure() {
|
||||
this.getChild(0).mayBeImpure() or
|
||||
@@ -721,7 +721,7 @@ class ReferenceDereferenceExpr extends Conversion, @ref_indirect {
|
||||
* A C++ `new` or `new[]` expression.
|
||||
*/
|
||||
class NewOrNewArrayExpr extends Expr, @any_new_expr {
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
|
||||
/**
|
||||
* Gets the `operator new` or `operator new[]` that allocates storage.
|
||||
@@ -898,7 +898,7 @@ class DeleteExpr extends Expr, @delete_expr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "DeleteExpr" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
|
||||
/**
|
||||
* Gets the compile-time type of the object being deleted.
|
||||
@@ -972,7 +972,7 @@ class DeleteArrayExpr extends Expr, @delete_array_expr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "DeleteArrayExpr" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
|
||||
/**
|
||||
* Gets the element type of the array being deleted.
|
||||
@@ -1216,3 +1216,18 @@ private predicate constantTemplateLiteral(Expr e) {
|
||||
or
|
||||
constantTemplateLiteral(e.(Cast).getExpr())
|
||||
}
|
||||
|
||||
/**
|
||||
* A C++ three-way comparison operation, also known as the _spaceship
|
||||
* operation_. This is specific to C++20 and later.
|
||||
* ```
|
||||
* auto c = (a <=> b);
|
||||
* ```
|
||||
*/
|
||||
class SpaceshipExpr extends BinaryOperation, @spaceshipexpr {
|
||||
override string getCanonicalQLClass() { result = "SpaceshipExpr" }
|
||||
|
||||
override int getPrecedence() { result = 11 }
|
||||
|
||||
override string getOperator() { result = "<=>" }
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class NotExpr extends UnaryLogicalOperation, @notexpr {
|
||||
|
||||
override string getCanonicalQLClass() { result = "NotExpr" }
|
||||
|
||||
override int getPrecedence() { result = 15 }
|
||||
override int getPrecedence() { result = 16 }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,6 +50,14 @@ private predicate ignoreExprAndDescendants(Expr expr) {
|
||||
// constant value.
|
||||
isIRConstant(getRealParent(expr))
|
||||
or
|
||||
// Only translate the initializer of a static local if it uses run-time data.
|
||||
// Otherwise the initializer does not run in function scope.
|
||||
exists(Initializer init, StaticStorageDurationVariable var |
|
||||
init = var.getInitializer() and
|
||||
var.hasConstantInitialization() and
|
||||
expr = init.getExpr().getFullyConverted()
|
||||
)
|
||||
or
|
||||
// Ignore descendants of `__assume` expressions, since we translated these to `NoOp`.
|
||||
getRealParent(expr) instanceof AssumeExpr
|
||||
or
|
||||
|
||||
@@ -93,17 +93,27 @@ private float wideningUpperBounds(ArithmeticType t) {
|
||||
|
||||
/**
|
||||
* Gets the value of the expression `e`, if it is a constant.
|
||||
* This predicate also handles the case of constant variables initialized in compilation units,
|
||||
* which doesn't necessarily have a getValue() result from the extractor.
|
||||
* This predicate also handles the case of constant variables initialized in different
|
||||
* compilation units, which doesn't necessarily have a getValue() result from the extractor.
|
||||
*/
|
||||
private string getValue(Expr e) {
|
||||
if exists(e.getValue())
|
||||
then result = e.getValue()
|
||||
else
|
||||
exists(VariableAccess access, Variable v |
|
||||
/*
|
||||
* It should be safe to propagate the initialization value to a variable if:
|
||||
* The type of v is const, and
|
||||
* The type of v is not volatile, and
|
||||
* Either:
|
||||
* v is a local/global variable, or
|
||||
* v is a static member variable
|
||||
*/
|
||||
|
||||
exists(VariableAccess access, StaticStorageDurationVariable v |
|
||||
not v.getUnderlyingType().isVolatile() and
|
||||
v.getUnderlyingType().isConst() and
|
||||
e = access and
|
||||
v = access.getTarget() and
|
||||
v.getUnderlyingType().isConst() and
|
||||
result = getValue(v.getAnAssignedValue())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,6 +99,8 @@ class ArrayExecFunctionCall extends FunctionCall {
|
||||
getTarget().hasGlobalName("execv") or
|
||||
getTarget().hasGlobalName("execvp") or
|
||||
getTarget().hasGlobalName("execvpe") or
|
||||
getTarget().hasGlobalName("execve") or
|
||||
getTarget().hasGlobalName("fexecve") or
|
||||
// Windows variants
|
||||
getTarget().hasGlobalName("_execv") or
|
||||
getTarget().hasGlobalName("_execve") or
|
||||
|
||||
@@ -1231,6 +1231,7 @@ funbind(
|
||||
| @ltexpr
|
||||
| @geexpr
|
||||
| @leexpr
|
||||
| @spaceshipexpr
|
||||
;
|
||||
|
||||
@bin_bitwise_op_expr = @lshiftexpr
|
||||
@@ -1636,6 +1637,7 @@ case @expr.kind of
|
||||
| 323 = @vec_fill
|
||||
| 324 = @builtinconvertvector
|
||||
| 325 = @builtincomplex
|
||||
| 326 = @spaceshipexpr
|
||||
;
|
||||
|
||||
new_allocated_type(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8349,3 +8349,184 @@ perf-regression.cpp:
|
||||
# 12| Type = [IntType] int
|
||||
# 12| Value = [Literal] 0
|
||||
# 12| ValueCategory = prvalue
|
||||
struct_init.cpp:
|
||||
# 1| [TopLevelFunction] int handler1(void*)
|
||||
# 1| params:
|
||||
# 1| 0: [Parameter] p
|
||||
# 1| Type = [VoidPointerType] void *
|
||||
# 2| [TopLevelFunction] int handler2(void*)
|
||||
# 2| params:
|
||||
# 2| 0: [Parameter] p
|
||||
# 2| Type = [VoidPointerType] void *
|
||||
# 4| [CopyAssignmentOperator] Info& Info::operator=(Info const&)
|
||||
# 4| params:
|
||||
#-----| 0: [Parameter] p#0
|
||||
#-----| Type = [LValueReferenceType] const Info &
|
||||
# 4| [MoveAssignmentOperator] Info& Info::operator=(Info&&)
|
||||
# 4| params:
|
||||
#-----| 0: [Parameter] p#0
|
||||
#-----| Type = [RValueReferenceType] Info &&
|
||||
# 16| [TopLevelFunction] void let_info_escape(Info*)
|
||||
# 16| params:
|
||||
# 16| 0: [Parameter] info
|
||||
# 16| Type = [PointerType] Info *
|
||||
# 16| body: [Block] { ... }
|
||||
# 17| 0: [ExprStmt] ExprStmt
|
||||
# 17| 0: [AssignExpr] ... = ...
|
||||
# 17| Type = [PointerType] Info *
|
||||
# 17| ValueCategory = lvalue
|
||||
# 17| 0: [VariableAccess] global_pointer
|
||||
# 17| Type = [PointerType] Info *
|
||||
# 17| ValueCategory = lvalue
|
||||
# 17| 1: [VariableAccess] info
|
||||
# 17| Type = [PointerType] Info *
|
||||
# 17| ValueCategory = prvalue(load)
|
||||
# 18| 1: [ReturnStmt] return ...
|
||||
# 20| [TopLevelFunction] void declare_static_infos()
|
||||
# 20| params:
|
||||
# 20| body: [Block] { ... }
|
||||
# 21| 0: [DeclStmt] declaration
|
||||
# 21| 0: [VariableDeclarationEntry] definition of static_infos
|
||||
# 21| Type = [ArrayType] Info[]
|
||||
# 21| init: [Initializer] initializer for static_infos
|
||||
# 21| expr: [ArrayAggregateLiteral] {...}
|
||||
# 21| Type = [ArrayType] Info[2]
|
||||
# 21| ValueCategory = prvalue
|
||||
# 22| [0]: [ClassAggregateLiteral] {...}
|
||||
# 22| Type = [Struct] Info
|
||||
# 22| ValueCategory = prvalue
|
||||
# 22| .name: [ArrayToPointerConversion] array to pointer conversion
|
||||
# 22| Type = [PointerType] const char *
|
||||
# 22| ValueCategory = prvalue
|
||||
# 22| expr: 1
|
||||
# 22| Type = [ArrayType] const char[2]
|
||||
# 22| Value = [StringLiteral] "1"
|
||||
# 22| ValueCategory = lvalue
|
||||
# 22| .handler: [FunctionAccess] handler1
|
||||
# 22| Type = [FunctionPointerType] ..(*)(..)
|
||||
# 22| ValueCategory = prvalue(load)
|
||||
# 23| [1]: [ClassAggregateLiteral] {...}
|
||||
# 23| Type = [Struct] Info
|
||||
# 23| ValueCategory = prvalue
|
||||
# 23| .name: [ArrayToPointerConversion] array to pointer conversion
|
||||
# 23| Type = [PointerType] const char *
|
||||
# 23| ValueCategory = prvalue
|
||||
# 23| expr: 2
|
||||
# 23| Type = [ArrayType] const char[2]
|
||||
# 23| Value = [StringLiteral] "2"
|
||||
# 23| ValueCategory = lvalue
|
||||
# 23| .handler: [AddressOfExpr] & ...
|
||||
# 23| Type = [FunctionPointerType] ..(*)(..)
|
||||
# 23| ValueCategory = prvalue
|
||||
# 23| 0: [FunctionAccess] handler2
|
||||
# 23| Type = [RoutineType] ..()(..)
|
||||
# 23| ValueCategory = lvalue
|
||||
# 25| 1: [ExprStmt] ExprStmt
|
||||
# 25| 0: [FunctionCall] call to let_info_escape
|
||||
# 25| Type = [VoidType] void
|
||||
# 25| ValueCategory = prvalue
|
||||
# 25| 0: [ArrayToPointerConversion] array to pointer conversion
|
||||
# 25| Type = [PointerType] Info *
|
||||
# 25| ValueCategory = prvalue
|
||||
# 25| expr: [VariableAccess] static_infos
|
||||
# 25| Type = [ArrayType] Info[2]
|
||||
# 25| ValueCategory = lvalue
|
||||
# 26| 2: [ReturnStmt] return ...
|
||||
# 28| [TopLevelFunction] void declare_local_infos()
|
||||
# 28| params:
|
||||
# 28| body: [Block] { ... }
|
||||
# 29| 0: [DeclStmt] declaration
|
||||
# 29| 0: [VariableDeclarationEntry] definition of local_infos
|
||||
# 29| Type = [ArrayType] Info[]
|
||||
# 29| init: [Initializer] initializer for local_infos
|
||||
# 29| expr: [ArrayAggregateLiteral] {...}
|
||||
# 29| Type = [ArrayType] Info[2]
|
||||
# 29| ValueCategory = prvalue
|
||||
# 30| [0]: [ClassAggregateLiteral] {...}
|
||||
# 30| Type = [Struct] Info
|
||||
# 30| ValueCategory = prvalue
|
||||
# 30| .name: [ArrayToPointerConversion] array to pointer conversion
|
||||
# 30| Type = [PointerType] const char *
|
||||
# 30| ValueCategory = prvalue
|
||||
# 30| expr: 1
|
||||
# 30| Type = [ArrayType] const char[2]
|
||||
# 30| Value = [StringLiteral] "1"
|
||||
# 30| ValueCategory = lvalue
|
||||
# 30| .handler: [FunctionAccess] handler1
|
||||
# 30| Type = [FunctionPointerType] ..(*)(..)
|
||||
# 30| ValueCategory = prvalue(load)
|
||||
# 31| [1]: [ClassAggregateLiteral] {...}
|
||||
# 31| Type = [Struct] Info
|
||||
# 31| ValueCategory = prvalue
|
||||
# 31| .name: [ArrayToPointerConversion] array to pointer conversion
|
||||
# 31| Type = [PointerType] const char *
|
||||
# 31| ValueCategory = prvalue
|
||||
# 31| expr: 2
|
||||
# 31| Type = [ArrayType] const char[2]
|
||||
# 31| Value = [StringLiteral] "2"
|
||||
# 31| ValueCategory = lvalue
|
||||
# 31| .handler: [AddressOfExpr] & ...
|
||||
# 31| Type = [FunctionPointerType] ..(*)(..)
|
||||
# 31| ValueCategory = prvalue
|
||||
# 31| 0: [FunctionAccess] handler2
|
||||
# 31| Type = [RoutineType] ..()(..)
|
||||
# 31| ValueCategory = lvalue
|
||||
# 33| 1: [ExprStmt] ExprStmt
|
||||
# 33| 0: [FunctionCall] call to let_info_escape
|
||||
# 33| Type = [VoidType] void
|
||||
# 33| ValueCategory = prvalue
|
||||
# 33| 0: [ArrayToPointerConversion] array to pointer conversion
|
||||
# 33| Type = [PointerType] Info *
|
||||
# 33| ValueCategory = prvalue
|
||||
# 33| expr: [VariableAccess] local_infos
|
||||
# 33| Type = [ArrayType] Info[2]
|
||||
# 33| ValueCategory = lvalue
|
||||
# 34| 2: [ReturnStmt] return ...
|
||||
# 36| [TopLevelFunction] void declare_static_runtime_infos(char const*)
|
||||
# 36| params:
|
||||
# 36| 0: [Parameter] name1
|
||||
# 36| Type = [PointerType] const char *
|
||||
# 36| body: [Block] { ... }
|
||||
# 37| 0: [DeclStmt] declaration
|
||||
# 37| 0: [VariableDeclarationEntry] definition of static_infos
|
||||
# 37| Type = [ArrayType] Info[]
|
||||
# 37| init: [Initializer] initializer for static_infos
|
||||
# 37| expr: [ArrayAggregateLiteral] {...}
|
||||
# 37| Type = [ArrayType] Info[2]
|
||||
# 37| ValueCategory = prvalue
|
||||
# 38| [0]: [ClassAggregateLiteral] {...}
|
||||
# 38| Type = [Struct] Info
|
||||
# 38| ValueCategory = prvalue
|
||||
# 38| .name: [VariableAccess] name1
|
||||
# 38| Type = [PointerType] const char *
|
||||
# 38| ValueCategory = prvalue(load)
|
||||
# 38| .handler: [FunctionAccess] handler1
|
||||
# 38| Type = [FunctionPointerType] ..(*)(..)
|
||||
# 38| ValueCategory = prvalue(load)
|
||||
# 39| [1]: [ClassAggregateLiteral] {...}
|
||||
# 39| Type = [Struct] Info
|
||||
# 39| ValueCategory = prvalue
|
||||
# 39| .name: [ArrayToPointerConversion] array to pointer conversion
|
||||
# 39| Type = [PointerType] const char *
|
||||
# 39| ValueCategory = prvalue
|
||||
# 39| expr: 2
|
||||
# 39| Type = [ArrayType] const char[2]
|
||||
# 39| Value = [StringLiteral] "2"
|
||||
# 39| ValueCategory = lvalue
|
||||
# 39| .handler: [AddressOfExpr] & ...
|
||||
# 39| Type = [FunctionPointerType] ..(*)(..)
|
||||
# 39| ValueCategory = prvalue
|
||||
# 39| 0: [FunctionAccess] handler2
|
||||
# 39| Type = [RoutineType] ..()(..)
|
||||
# 39| ValueCategory = lvalue
|
||||
# 41| 1: [ExprStmt] ExprStmt
|
||||
# 41| 0: [FunctionCall] call to let_info_escape
|
||||
# 41| Type = [VoidType] void
|
||||
# 41| ValueCategory = prvalue
|
||||
# 41| 0: [ArrayToPointerConversion] array to pointer conversion
|
||||
# 41| Type = [PointerType] Info *
|
||||
# 41| ValueCategory = prvalue
|
||||
# 41| expr: [VariableAccess] static_infos
|
||||
# 41| Type = [ArrayType] Info[2]
|
||||
# 41| ValueCategory = lvalue
|
||||
# 42| 2: [ReturnStmt] return ...
|
||||
|
||||
@@ -6272,3 +6272,131 @@ perf-regression.cpp:
|
||||
# 9| v9_7(void) = UnmodeledUse : mu*
|
||||
# 9| v9_8(void) = AliasedUse : ~mu9_4
|
||||
# 9| v9_9(void) = ExitFunction :
|
||||
|
||||
struct_init.cpp:
|
||||
# 16| void let_info_escape(Info*)
|
||||
# 16| Block 0
|
||||
# 16| v16_1(void) = EnterFunction :
|
||||
# 16| mu16_2(unknown) = AliasedDefinition :
|
||||
# 16| mu16_3(unknown) = InitializeNonLocal :
|
||||
# 16| mu16_4(unknown) = UnmodeledDefinition :
|
||||
# 16| r16_5(glval<Info *>) = VariableAddress[info] :
|
||||
# 16| mu16_6(Info *) = InitializeParameter[info] : &:r16_5
|
||||
# 16| r16_7(Info *) = Load : &:r16_5, ~mu16_6
|
||||
# 16| mu16_8(unknown) = InitializeIndirection[info] : &:r16_7
|
||||
# 17| r17_1(glval<Info *>) = VariableAddress[info] :
|
||||
# 17| r17_2(Info *) = Load : &:r17_1, ~mu16_4
|
||||
# 17| r17_3(glval<Info *>) = VariableAddress[global_pointer] :
|
||||
# 17| mu17_4(Info *) = Store : &:r17_3, r17_2
|
||||
# 18| v18_1(void) = NoOp :
|
||||
# 16| v16_9(void) = ReturnIndirection : &:r16_7, ~mu16_4
|
||||
# 16| v16_10(void) = ReturnVoid :
|
||||
# 16| v16_11(void) = UnmodeledUse : mu*
|
||||
# 16| v16_12(void) = AliasedUse : ~mu16_4
|
||||
# 16| v16_13(void) = ExitFunction :
|
||||
|
||||
# 20| void declare_static_infos()
|
||||
# 20| Block 0
|
||||
# 20| v20_1(void) = EnterFunction :
|
||||
# 20| mu20_2(unknown) = AliasedDefinition :
|
||||
# 20| mu20_3(unknown) = InitializeNonLocal :
|
||||
# 20| mu20_4(unknown) = UnmodeledDefinition :
|
||||
# 21| r21_1(glval<Info[2]>) = VariableAddress[static_infos] :
|
||||
# 21| mu21_2(Info[2]) = Uninitialized[static_infos] : &:r21_1
|
||||
# 25| r25_1(glval<unknown>) = FunctionAddress[let_info_escape] :
|
||||
# 25| r25_2(glval<Info[2]>) = VariableAddress[static_infos] :
|
||||
# 25| r25_3(Info *) = Convert : r25_2
|
||||
# 25| v25_4(void) = Call : func:r25_1, 0:r25_3
|
||||
# 25| mu25_5(unknown) = ^CallSideEffect : ~mu20_4
|
||||
# 25| v25_6(void) = ^BufferReadSideEffect[0] : &:r25_3, ~mu20_4
|
||||
# 25| mu25_7(unknown) = ^BufferMayWriteSideEffect[0] : &:r25_3
|
||||
# 26| v26_1(void) = NoOp :
|
||||
# 20| v20_5(void) = ReturnVoid :
|
||||
# 20| v20_6(void) = UnmodeledUse : mu*
|
||||
# 20| v20_7(void) = AliasedUse : ~mu20_4
|
||||
# 20| v20_8(void) = ExitFunction :
|
||||
|
||||
# 28| void declare_local_infos()
|
||||
# 28| Block 0
|
||||
# 28| v28_1(void) = EnterFunction :
|
||||
# 28| mu28_2(unknown) = AliasedDefinition :
|
||||
# 28| mu28_3(unknown) = InitializeNonLocal :
|
||||
# 28| mu28_4(unknown) = UnmodeledDefinition :
|
||||
# 29| r29_1(glval<Info[2]>) = VariableAddress[local_infos] :
|
||||
# 29| mu29_2(Info[2]) = Uninitialized[local_infos] : &:r29_1
|
||||
# 29| r29_3(int) = Constant[0] :
|
||||
# 29| r29_4(glval<Info>) = PointerAdd[16] : r29_1, r29_3
|
||||
# 30| r30_1(glval<char *>) = FieldAddress[name] : r29_4
|
||||
# 30| r30_2(glval<char[2]>) = StringConstant["1"] :
|
||||
# 30| r30_3(char *) = Convert : r30_2
|
||||
# 30| mu30_4(char *) = Store : &:r30_1, r30_3
|
||||
# 30| r30_5(glval<..(*)(..)>) = FieldAddress[handler] : r29_4
|
||||
# 30| r30_6(..(*)(..)) = FunctionAddress[handler1] :
|
||||
# 30| mu30_7(..(*)(..)) = Store : &:r30_5, r30_6
|
||||
# 29| r29_5(int) = Constant[1] :
|
||||
# 29| r29_6(glval<Info>) = PointerAdd[16] : r29_1, r29_5
|
||||
# 31| r31_1(glval<char *>) = FieldAddress[name] : r29_6
|
||||
# 31| r31_2(glval<char[2]>) = StringConstant["2"] :
|
||||
# 31| r31_3(char *) = Convert : r31_2
|
||||
# 31| mu31_4(char *) = Store : &:r31_1, r31_3
|
||||
# 31| r31_5(glval<..(*)(..)>) = FieldAddress[handler] : r29_6
|
||||
# 31| r31_6(glval<..()(..)>) = FunctionAddress[handler2] :
|
||||
# 31| r31_7(..(*)(..)) = CopyValue : r31_6
|
||||
# 31| mu31_8(..(*)(..)) = Store : &:r31_5, r31_7
|
||||
# 33| r33_1(glval<unknown>) = FunctionAddress[let_info_escape] :
|
||||
# 33| r33_2(glval<Info[2]>) = VariableAddress[local_infos] :
|
||||
# 33| r33_3(Info *) = Convert : r33_2
|
||||
# 33| v33_4(void) = Call : func:r33_1, 0:r33_3
|
||||
# 33| mu33_5(unknown) = ^CallSideEffect : ~mu28_4
|
||||
# 33| v33_6(void) = ^BufferReadSideEffect[0] : &:r33_3, ~mu28_4
|
||||
# 33| mu33_7(unknown) = ^BufferMayWriteSideEffect[0] : &:r33_3
|
||||
# 34| v34_1(void) = NoOp :
|
||||
# 28| v28_5(void) = ReturnVoid :
|
||||
# 28| v28_6(void) = UnmodeledUse : mu*
|
||||
# 28| v28_7(void) = AliasedUse : ~mu28_4
|
||||
# 28| v28_8(void) = ExitFunction :
|
||||
|
||||
# 36| void declare_static_runtime_infos(char const*)
|
||||
# 36| Block 0
|
||||
# 36| v36_1(void) = EnterFunction :
|
||||
# 36| mu36_2(unknown) = AliasedDefinition :
|
||||
# 36| mu36_3(unknown) = InitializeNonLocal :
|
||||
# 36| mu36_4(unknown) = UnmodeledDefinition :
|
||||
# 36| r36_5(glval<char *>) = VariableAddress[name1] :
|
||||
# 36| mu36_6(char *) = InitializeParameter[name1] : &:r36_5
|
||||
# 36| r36_7(char *) = Load : &:r36_5, ~mu36_6
|
||||
# 36| mu36_8(unknown) = InitializeIndirection[name1] : &:r36_7
|
||||
# 37| r37_1(glval<Info[2]>) = VariableAddress[static_infos] :
|
||||
# 37| mu37_2(Info[2]) = Uninitialized[static_infos] : &:r37_1
|
||||
# 37| r37_3(int) = Constant[0] :
|
||||
# 37| r37_4(glval<Info>) = PointerAdd[16] : r37_1, r37_3
|
||||
# 38| r38_1(glval<char *>) = FieldAddress[name] : r37_4
|
||||
# 38| r38_2(glval<char *>) = VariableAddress[name1] :
|
||||
# 38| r38_3(char *) = Load : &:r38_2, ~mu36_4
|
||||
# 38| mu38_4(char *) = Store : &:r38_1, r38_3
|
||||
# 38| r38_5(glval<..(*)(..)>) = FieldAddress[handler] : r37_4
|
||||
# 38| r38_6(..(*)(..)) = FunctionAddress[handler1] :
|
||||
# 38| mu38_7(..(*)(..)) = Store : &:r38_5, r38_6
|
||||
# 37| r37_5(int) = Constant[1] :
|
||||
# 37| r37_6(glval<Info>) = PointerAdd[16] : r37_1, r37_5
|
||||
# 39| r39_1(glval<char *>) = FieldAddress[name] : r37_6
|
||||
# 39| r39_2(glval<char[2]>) = StringConstant["2"] :
|
||||
# 39| r39_3(char *) = Convert : r39_2
|
||||
# 39| mu39_4(char *) = Store : &:r39_1, r39_3
|
||||
# 39| r39_5(glval<..(*)(..)>) = FieldAddress[handler] : r37_6
|
||||
# 39| r39_6(glval<..()(..)>) = FunctionAddress[handler2] :
|
||||
# 39| r39_7(..(*)(..)) = CopyValue : r39_6
|
||||
# 39| mu39_8(..(*)(..)) = Store : &:r39_5, r39_7
|
||||
# 41| r41_1(glval<unknown>) = FunctionAddress[let_info_escape] :
|
||||
# 41| r41_2(glval<Info[2]>) = VariableAddress[static_infos] :
|
||||
# 41| r41_3(Info *) = Convert : r41_2
|
||||
# 41| v41_4(void) = Call : func:r41_1, 0:r41_3
|
||||
# 41| mu41_5(unknown) = ^CallSideEffect : ~mu36_4
|
||||
# 41| v41_6(void) = ^BufferReadSideEffect[0] : &:r41_3, ~mu36_4
|
||||
# 41| mu41_7(unknown) = ^BufferMayWriteSideEffect[0] : &:r41_3
|
||||
# 42| v42_1(void) = NoOp :
|
||||
# 36| v36_9(void) = ReturnIndirection : &:r36_7, ~mu36_4
|
||||
# 36| v36_10(void) = ReturnVoid :
|
||||
# 36| v36_11(void) = UnmodeledUse : mu*
|
||||
# 36| v36_12(void) = AliasedUse : ~mu36_4
|
||||
# 36| v36_13(void) = ExitFunction :
|
||||
|
||||
42
cpp/ql/test/library-tests/ir/ir/struct_init.cpp
Normal file
42
cpp/ql/test/library-tests/ir/ir/struct_init.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
int handler1(void *p);
|
||||
int handler2(void *p);
|
||||
|
||||
struct Info {
|
||||
const char *name;
|
||||
int (*handler)(void *);
|
||||
};
|
||||
|
||||
static Info infos_in_file[] = {
|
||||
{ "1", handler1 },
|
||||
{ "3", &handler2 },
|
||||
};
|
||||
|
||||
Info *global_pointer;
|
||||
|
||||
void let_info_escape(Info *info) {
|
||||
global_pointer = info;
|
||||
}
|
||||
|
||||
void declare_static_infos() {
|
||||
static Info static_infos[] = {
|
||||
{ "1", handler1 },
|
||||
{ "2", &handler2 },
|
||||
};
|
||||
let_info_escape(static_infos);
|
||||
}
|
||||
|
||||
void declare_local_infos() {
|
||||
Info local_infos[] = {
|
||||
{ "1", handler1 },
|
||||
{ "2", &handler2 },
|
||||
};
|
||||
let_info_escape(local_infos);
|
||||
}
|
||||
|
||||
void declare_static_runtime_infos(const char *name1) {
|
||||
static Info static_infos[] = {
|
||||
{ name1, handler1 },
|
||||
{ "2", &handler2 },
|
||||
};
|
||||
let_info_escape(static_infos);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// This header file is extracted only once even though it's included by both
|
||||
// file1.c and file2.c. That's presumably because it's wrongly considered to
|
||||
// expand to the same trap in both contexts. In practice, this header gets
|
||||
// extracted together with the extraction of file1.c.
|
||||
|
||||
// BUG: types of members depend on extraction order.
|
||||
// Only one copy of this struct is extracted, and the types of its members refer
|
||||
// to the typedefs in file1.c. Had file2.c been extracted first instead, the
|
||||
// types of its members would be different.
|
||||
struct UnifiableOnce {
|
||||
intAlias intMember;
|
||||
qualifiedIntAlias qualifiedIntMember;
|
||||
};
|
||||
|
||||
// BUG: types of parameters depend on extraction order.
|
||||
void functionOnce(intAlias param);
|
||||
@@ -0,0 +1,30 @@
|
||||
// This header file is extracted twice because its inclusions in file1.c and
|
||||
// file2.c lead to different context hashes, seemingly because this file (unlike
|
||||
// extracted_once.h) refers to `structAlias`. That means the resulting trap has
|
||||
// two copies of all declarations in this file, and those copies have to be
|
||||
// unified in the trap import step or in QL.
|
||||
|
||||
// GOOD. The types of the members of this struct are unifiable, which in this
|
||||
// context means that they share the same unspecified types. This means that the
|
||||
// two extractions of the struct get the same content hash and therefore become
|
||||
// one entry in the database. Both struct members have multiple types in the
|
||||
// `membervariables` table, but those are unified in the
|
||||
// `MemberVariable.getType()` predicate.
|
||||
struct UnifiableTwice {
|
||||
intAlias intMember;
|
||||
qualifiedIntAlias qualifiedIntMember;
|
||||
};
|
||||
|
||||
// BUG: Non-member variables of this type have two types in the database.
|
||||
// The type of `structMember` is ambiguous, and the two possible types are not
|
||||
// unifiable, meaning in this context that they don't share an unspecified type.
|
||||
// The types are nevertheless _compatible_, so it's valid C (not C++) to use
|
||||
// these two definitions interchangably in the same program.
|
||||
struct NotUnifiableTwice {
|
||||
structAlias structMember;
|
||||
};
|
||||
|
||||
// BUG: The parameter of this function has two types.
|
||||
// Because the `MemberVariable.getType()` workaround does not apply to a
|
||||
// `Parameter`, this `Parameter` gets two types.
|
||||
void functionTwice(intAlias param);
|
||||
@@ -0,0 +1,15 @@
|
||||
// These typedefs are all _compatible_ (see
|
||||
// https://en.cppreference.com/w/c/language/type#Compatible_types) with their
|
||||
// siblings in file2.c. It varies whether they have a canonical form that's
|
||||
// common to them both.
|
||||
typedef int localInt;
|
||||
typedef localInt intAlias; // has common `getUnderlyingType()` and `getUnspecifiedType()`
|
||||
typedef int qualifiedIntAlias; // only has common `getUnspecifiedType()`
|
||||
typedef struct emptyStruct1 { } structAlias; // has no common type
|
||||
|
||||
#include "extracted_once.h"
|
||||
struct UnifiableOnce uOnce;
|
||||
|
||||
#include "extracted_twice.h"
|
||||
struct UnifiableTwice uTwice;
|
||||
struct NotUnifiableTwice nTwice; // BUG: this variable has two types
|
||||
@@ -0,0 +1,10 @@
|
||||
typedef int intAlias;
|
||||
typedef const int qualifiedIntAlias;
|
||||
typedef struct emptyStruct2 { } structAlias;
|
||||
|
||||
#include "extracted_once.h"
|
||||
struct UnifiableOnce uOnce;
|
||||
|
||||
#include "extracted_twice.h"
|
||||
struct UnifiableTwice uTwice;
|
||||
struct NotUnifiableTwice nTwice; // BUG: this variable has two types
|
||||
@@ -0,0 +1,17 @@
|
||||
| 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_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 |
|
||||
| extracted_twice.h:24:17:24:28 | structMember | file2.c:3:33:3:43 | structAlias | 1 |
|
||||
| extracted_twice.h:30:29:30:33 | param | file1.c:6:18:6:25 | intAlias | 2 |
|
||||
| extracted_twice.h:30:29:30:33 | param | file2.c:1:13:1:20 | intAlias | 2 |
|
||||
| file1.c:11:22:11:26 | uOnce | extracted_once.h:10:8:10:20 | UnifiableOnce | 1 |
|
||||
| file1.c:14:23:14:28 | uTwice | extracted_twice.h:13:8:13:21 | UnifiableTwice | 1 |
|
||||
| file1.c:15:26:15:31 | nTwice | extracted_twice.h:23:8:23:24 | NotUnifiableTwice | 2 |
|
||||
| file1.c:15:26:15:31 | nTwice | extracted_twice.h:23:8:23:24 | NotUnifiableTwice | 2 |
|
||||
| file2.c:6:22:6:26 | uOnce | extracted_once.h:10:8:10:20 | UnifiableOnce | 1 |
|
||||
| file2.c:9:23:9:28 | uTwice | extracted_twice.h:13:8:13:21 | UnifiableTwice | 1 |
|
||||
| file2.c:10:26:10:31 | nTwice | extracted_twice.h:23:8:23:24 | NotUnifiableTwice | 2 |
|
||||
| file2.c:10:26:10:31 | nTwice | extracted_twice.h:23:8:23:24 | NotUnifiableTwice | 2 |
|
||||
@@ -0,0 +1,5 @@
|
||||
import cpp
|
||||
|
||||
from Variable var
|
||||
where exists(var.getFile().getRelativePath())
|
||||
select var, var.getType(), strictcount(var.getType())
|
||||
@@ -137,8 +137,12 @@
|
||||
| test.c:109:14:109:16 | Constant: 44 | positive strictlyPositive |
|
||||
| test.c:110:14:110:14 | Constant: 1 | positive strictlyPositive |
|
||||
| test.c:110:14:110:14 | Store: 1 | positive strictlyPositive |
|
||||
| test.c:118:20:118:20 | Uninitialized: definition of n | positive |
|
||||
| test.c:119:10:119:10 | Load: n | positive |
|
||||
| test.c:119:10:119:12 | Add: ... ++ | positive strictlyPositive |
|
||||
| test.c:119:10:119:12 | Constant: ... ++ | positive strictlyPositive |
|
||||
| test.c:119:10:119:12 | CopyValue: ... ++ | positive |
|
||||
| test.c:119:10:119:12 | Store: ... ++ | positive |
|
||||
| test.c:119:10:119:12 | Store: ... ++ | positive strictlyPositive |
|
||||
| test.c:124:11:124:15 | Load: Start | positive |
|
||||
| test.c:124:11:124:15 | Phi: Start | positive |
|
||||
|
||||
@@ -47,7 +47,6 @@ instructionWithoutSuccessor
|
||||
| ms_try_mix.cpp:11:12:11:15 | Chi: call to C |
|
||||
| ms_try_mix.cpp:28:12:28:15 | Chi: call to C |
|
||||
| ms_try_mix.cpp:48:10:48:13 | Chi: call to C |
|
||||
| pointer_to_member.cpp:36:11:36:30 | FieldAddress: {...} |
|
||||
| stmt_expr.cpp:27:5:27:15 | Store: ... = ... |
|
||||
| vla.c:5:9:5:14 | Uninitialized: definition of matrix |
|
||||
| vla.c:11:6:11:16 | UnmodeledDefinition: vla_typedef |
|
||||
|
||||
@@ -34,8 +34,6 @@ missingOperand
|
||||
| misc.c:220:3:223:3 | Store: ... = ... | Instruction 'Store' is missing an expected operand with tag 'StoreValue' in function '$@'. | misc.c:219:5:219:26 | IR: assign_designated_init | int assign_designated_init(someStruct*) |
|
||||
| misc.c:220:9:223:3 | FieldAddress: {...} | Instruction 'FieldAddress' is missing an expected operand with tag 'Unary' in function '$@'. | misc.c:219:5:219:26 | IR: assign_designated_init | int assign_designated_init(someStruct*) |
|
||||
| misc.c:220:9:223:3 | FieldAddress: {...} | Instruction 'FieldAddress' is missing an expected operand with tag 'Unary' in function '$@'. | misc.c:219:5:219:26 | IR: assign_designated_init | int assign_designated_init(someStruct*) |
|
||||
| pointer_to_member.cpp:36:13:36:19 | FieldAddress: x1 | Instruction 'FieldAddress' is missing an expected operand with tag 'Unary' in function '$@'. | pointer_to_member.cpp:32:6:32:14 | IR: pmIsConst | void pmIsConst() |
|
||||
| pointer_to_member.cpp:36:22:36:28 | Store: f1 | Instruction 'Store' is missing an expected operand with tag 'StoreValue' in function '$@'. | pointer_to_member.cpp:32:6:32:14 | IR: pmIsConst | void pmIsConst() |
|
||||
| range_analysis.c:368:10:368:21 | Store: ... ? ... : ... | Instruction 'Store' is missing an expected operand with tag 'StoreValue' in function '$@'. | range_analysis.c:355:14:355:27 | IR: test_ternary01 | unsigned int test_ternary01(unsigned int) |
|
||||
| range_analysis.c:369:10:369:36 | Store: ... ? ... : ... | Instruction 'Store' is missing an expected operand with tag 'StoreValue' in function '$@'. | range_analysis.c:355:14:355:27 | IR: test_ternary01 | unsigned int test_ternary01(unsigned int) |
|
||||
| range_analysis.c:370:10:370:38 | Store: ... ? ... : ... | Instruction 'Store' is missing an expected operand with tag 'StoreValue' in function '$@'. | range_analysis.c:355:14:355:27 | IR: test_ternary01 | unsigned int test_ternary01(unsigned int) |
|
||||
@@ -93,16 +91,6 @@ instructionWithoutSuccessor
|
||||
| ms_try_mix.cpp:48:10:48:13 | IndirectMayWriteSideEffect: call to C |
|
||||
| ms_try_mix.cpp:51:5:51:11 | ThrowValue: throw ... |
|
||||
| ms_try_mix.cpp:53:13:54:3 | NoOp: { ... } |
|
||||
| pointer_to_member.cpp:36:11:36:30 | FieldAddress: {...} |
|
||||
| pointer_to_member.cpp:36:11:36:30 | FieldAddress: {...} |
|
||||
| static_init_templates.cpp:80:27:80:36 | Convert: (void *)... |
|
||||
| static_init_templates.cpp:80:27:80:36 | Convert: (void *)... |
|
||||
| static_init_templates.cpp:89:27:89:36 | Convert: (void *)... |
|
||||
| static_init_templates.cpp:89:27:89:36 | Convert: (void *)... |
|
||||
| static_init_templates.cpp:97:27:97:36 | Convert: (void *)... |
|
||||
| static_init_templates.cpp:97:27:97:36 | Convert: (void *)... |
|
||||
| static_init_templates.cpp:105:27:105:27 | Constant: (void *)... |
|
||||
| static_init_templates.cpp:105:27:105:27 | Constant: (void *)... |
|
||||
| stmt_expr.cpp:27:5:27:15 | Store: ... = ... |
|
||||
| stmt_expr.cpp:29:11:32:11 | CopyValue: (statement expression) |
|
||||
| stmt_in_type.cpp:5:53:5:53 | Constant: 1 |
|
||||
@@ -640,9 +628,6 @@ backEdgeCountMismatch
|
||||
useNotDominatedByDefinition
|
||||
| VacuousDestructorCall.cpp:2:29:2:29 | Address | Operand 'Address' is not dominated by its definition in function '$@'. | VacuousDestructorCall.cpp:2:6:2:6 | IR: CallDestructor | void CallDestructor<int>(int, int*) |
|
||||
| misc.c:219:47:219:48 | Address | Operand 'Address' is not dominated by its definition in function '$@'. | misc.c:219:5:219:26 | IR: assign_designated_init | int assign_designated_init(someStruct*) |
|
||||
| pointer_to_member.cpp:36:11:36:30 | Unary | Operand 'Unary' is not dominated by its definition in function '$@'. | pointer_to_member.cpp:32:6:32:14 | IR: pmIsConst | void pmIsConst() |
|
||||
| pointer_to_member.cpp:36:13:36:19 | Address | Operand 'Address' is not dominated by its definition in function '$@'. | pointer_to_member.cpp:32:6:32:14 | IR: pmIsConst | void pmIsConst() |
|
||||
| pointer_to_member.cpp:36:22:36:28 | Address | Operand 'Address' is not dominated by its definition in function '$@'. | pointer_to_member.cpp:32:6:32:14 | IR: pmIsConst | void pmIsConst() |
|
||||
| try_catch.cpp:21:13:21:24 | Address | Operand 'Address' is not dominated by its definition in function '$@'. | try_catch.cpp:19:6:19:23 | IR: throw_from_nonstmt | void throw_from_nonstmt(int) |
|
||||
| vla.c:3:27:3:30 | Address | Operand 'Address' is not dominated by its definition in function '$@'. | vla.c:3:5:3:8 | IR: main | int main(int, char**) |
|
||||
switchInstructionWithoutDefaultEdge
|
||||
|
||||
@@ -56,7 +56,6 @@ instructionWithoutSuccessor
|
||||
| ms_try_mix.cpp:11:12:11:15 | IndirectMayWriteSideEffect: call to C |
|
||||
| ms_try_mix.cpp:28:12:28:15 | IndirectMayWriteSideEffect: call to C |
|
||||
| ms_try_mix.cpp:48:10:48:13 | IndirectMayWriteSideEffect: call to C |
|
||||
| pointer_to_member.cpp:36:11:36:30 | FieldAddress: {...} |
|
||||
| stmt_expr.cpp:27:5:27:15 | Store: ... = ... |
|
||||
| vla.c:5:9:5:14 | Uninitialized: definition of matrix |
|
||||
| vla.c:11:6:11:16 | UnmodeledDefinition: vla_typedef |
|
||||
|
||||
@@ -7,37 +7,65 @@
|
||||
| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const address & | SemanticStackVariable | | |
|
||||
| file://:0:0:0:0 | reg_save_area | file://:0:0:0:0 | void * | Field | | |
|
||||
| variables.cpp:1:12:1:12 | i | file://:0:0:0:0 | int | GlobalVariable | | |
|
||||
| variables.cpp:1:12:1:12 | i | file://:0:0:0:0 | int | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:2:12:2:12 | i | file://:0:0:0:0 | int | GlobalVariable | | |
|
||||
| variables.cpp:2:12:2:12 | i | file://:0:0:0:0 | int | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:3:12:3:12 | i | file://:0:0:0:0 | int | GlobalVariable | | |
|
||||
| variables.cpp:3:12:3:12 | i | file://:0:0:0:0 | int | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:5:11:5:11 | c | file://:0:0:0:0 | const int | GlobalVariable | const | static |
|
||||
| variables.cpp:5:11:5:11 | c | file://:0:0:0:0 | const int | StaticStorageDurationVariable | const | static |
|
||||
| variables.cpp:6:14:6:15 | pi | file://:0:0:0:0 | const double | GlobalVariable | const | static |
|
||||
| variables.cpp:6:14:6:15 | pi | file://:0:0:0:0 | const double | StaticStorageDurationVariable | const | static |
|
||||
| variables.cpp:8:10:8:10 | a | file://:0:0:0:0 | unsigned int | GlobalVariable | | |
|
||||
| variables.cpp:8:10:8:10 | a | file://:0:0:0:0 | unsigned int | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:10:14:10:14 | b | file://:0:0:0:0 | unsigned int | GlobalVariable | | |
|
||||
| variables.cpp:10:14:10:14 | b | file://:0:0:0:0 | unsigned int | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:12:13:12:17 | kings | file://:0:0:0:0 | const char *[] | GlobalVariable | | |
|
||||
| variables.cpp:12:13:12:17 | kings | file://:0:0:0:0 | const char *[] | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:14:6:14:6 | p | file://:0:0:0:0 | int * | GlobalVariable | | |
|
||||
| variables.cpp:14:6:14:6 | p | file://:0:0:0:0 | int * | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:14:9:14:9 | q | file://:0:0:0:0 | int | GlobalVariable | | |
|
||||
| variables.cpp:14:9:14:9 | q | file://:0:0:0:0 | int | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:15:12:15:13 | v1 | file://:0:0:0:0 | int[10] | GlobalVariable | | static |
|
||||
| variables.cpp:15:12:15:13 | v1 | file://:0:0:0:0 | int[10] | StaticStorageDurationVariable | | static |
|
||||
| variables.cpp:15:21:15:22 | pv | file://:0:0:0:0 | int * | GlobalVariable | | static |
|
||||
| variables.cpp:15:21:15:22 | pv | file://:0:0:0:0 | int * | StaticStorageDurationVariable | | static |
|
||||
| variables.cpp:17:7:17:8 | fp | file://:0:0:0:0 | ..(*)(..) | FunctionPointerVariable | | |
|
||||
| variables.cpp:17:7:17:8 | fp | file://:0:0:0:0 | ..(*)(..) | GlobalVariable | | |
|
||||
| variables.cpp:17:7:17:8 | fp | file://:0:0:0:0 | ..(*)(..) | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:19:7:19:8 | v2 | file://:0:0:0:0 | float[3] | GlobalVariable | | |
|
||||
| variables.cpp:19:7:19:8 | v2 | file://:0:0:0:0 | float[3] | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:20:7:20:8 | v3 | file://:0:0:0:0 | char *[32] | GlobalVariable | | |
|
||||
| variables.cpp:20:7:20:8 | v3 | file://:0:0:0:0 | char *[32] | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:22:5:22:6 | d2 | file://:0:0:0:0 | int[10][20] | GlobalVariable | | |
|
||||
| variables.cpp:22:5:22:6 | d2 | file://:0:0:0:0 | int[10][20] | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:24:6:24:7 | v4 | file://:0:0:0:0 | char[3] | GlobalVariable | | |
|
||||
| variables.cpp:24:6:24:7 | v4 | file://:0:0:0:0 | char[3] | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:26:5:26:6 | v5 | file://:0:0:0:0 | int[8] | GlobalVariable | | |
|
||||
| variables.cpp:26:5:26:6 | v5 | file://:0:0:0:0 | int[8] | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:28:7:28:8 | p2 | file://:0:0:0:0 | char * | GlobalVariable | | |
|
||||
| variables.cpp:28:7:28:8 | p2 | file://:0:0:0:0 | char * | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:29:6:29:7 | p3 | file://:0:0:0:0 | char[] | GlobalVariable | | |
|
||||
| variables.cpp:29:6:29:7 | p3 | file://:0:0:0:0 | char[] | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:31:6:31:10 | alpha | file://:0:0:0:0 | char[] | GlobalVariable | | |
|
||||
| variables.cpp:31:6:31:10 | alpha | file://:0:0:0:0 | char[] | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:34:5:34:6 | av | file://:0:0:0:0 | int[] | GlobalVariable | | |
|
||||
| variables.cpp:34:5:34:6 | av | file://:0:0:0:0 | int[] | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:35:6:35:8 | ap1 | file://:0:0:0:0 | int * | GlobalVariable | | |
|
||||
| variables.cpp:35:6:35:8 | ap1 | file://:0:0:0:0 | int * | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:36:6:36:8 | ap2 | file://:0:0:0:0 | int * | GlobalVariable | | |
|
||||
| variables.cpp:36:6:36:8 | ap2 | file://:0:0:0:0 | int * | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:37:6:37:8 | ap3 | file://:0:0:0:0 | int * | GlobalVariable | | |
|
||||
| variables.cpp:37:6:37:8 | ap3 | file://:0:0:0:0 | int * | StaticStorageDurationVariable | | |
|
||||
| variables.cpp:41:7:41:11 | local | file://:0:0:0:0 | char[] | LocalVariable | | |
|
||||
| variables.cpp:41:7:41:11 | local | file://:0:0:0:0 | char[] | SemanticStackVariable | | |
|
||||
| variables.cpp:43:14:43:18 | local | file://:0:0:0:0 | int | LocalVariable | | static |
|
||||
| variables.cpp:43:14:43:18 | local | file://:0:0:0:0 | int | StaticStorageDurationVariable | | static |
|
||||
| variables.cpp:48:9:48:12 | name | file://:0:0:0:0 | char * | Field | | |
|
||||
| variables.cpp:49:12:49:17 | number | file://:0:0:0:0 | long | Field | | |
|
||||
| variables.cpp:50:9:50:14 | street | file://:0:0:0:0 | char * | Field | | |
|
||||
| variables.cpp:51:9:51:12 | town | file://:0:0:0:0 | char * | Field | | |
|
||||
| variables.cpp:52:16:52:22 | country | file://:0:0:0:0 | char * | MemberVariable | | static |
|
||||
| variables.cpp:52:16:52:22 | country | file://:0:0:0:0 | char * | StaticStorageDurationVariable | | static |
|
||||
| variables.cpp:56:14:56:29 | externInFunction | file://:0:0:0:0 | int | GlobalVariable | | |
|
||||
| variables.cpp:56:14:56:29 | externInFunction | file://:0:0:0:0 | int | StaticStorageDurationVariable | | |
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
| test2.cpp:19:3:19:6 | call to free | There is a new/free mismatch between this free and the corresponding $@. | test2.cpp:18:12:18:18 | new | new |
|
||||
| test2.cpp:26:3:26:6 | call to free | There is a new/free mismatch between this free and the corresponding $@. | test2.cpp:25:7:25:13 | new | new |
|
||||
| test.cpp:36:2:36:17 | delete | There is a malloc/delete mismatch between this delete and the corresponding $@. | test.cpp:27:18:27:23 | call to malloc | malloc |
|
||||
| test.cpp:41:2:41:5 | call to free | There is a new/free mismatch between this free and the corresponding $@. | test.cpp:26:7:26:17 | new | new |
|
||||
| test.cpp:68:3:68:11 | delete | There is a malloc/delete mismatch between this delete and the corresponding $@. | test.cpp:64:28:64:33 | call to malloc | malloc |
|
||||
|
||||
36
cpp/ql/test/query-tests/Critical/NewFree/test2.cpp
Normal file
36
cpp/ql/test/query-tests/Critical/NewFree/test2.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// semmle-extractor-options: -std=gnu++14
|
||||
|
||||
typedef unsigned long size_t;
|
||||
|
||||
void *malloc(size_t size);
|
||||
void free(void *ptr);
|
||||
|
||||
void* operator new(size_t _Size, void *_Where);
|
||||
|
||||
// ---
|
||||
|
||||
template<typename T>
|
||||
class MyTest2Class
|
||||
{
|
||||
public:
|
||||
MyTest2Class()
|
||||
{
|
||||
int *a = new int;
|
||||
free(a); // BAD
|
||||
|
||||
int *ptr_b = (int *)malloc(sizeof(int));
|
||||
int *b = new(ptr_b) int;
|
||||
free(b); // GOOD
|
||||
|
||||
c = new int;
|
||||
free(c); // BAD
|
||||
|
||||
int *ptr_d = (int *)malloc(sizeof(int));
|
||||
d = new(ptr_d) int;
|
||||
free(d); // GOOD
|
||||
}
|
||||
|
||||
int *c, *d;
|
||||
};
|
||||
|
||||
MyTest2Class<int> mt2c_i;
|
||||
@@ -0,0 +1,29 @@
|
||||
void func_with_default_arg(const int n = 0) {
|
||||
if(n <= 10) {}
|
||||
}
|
||||
|
||||
struct A {
|
||||
const int int_member = 0;
|
||||
A(int n) : int_member(n) {
|
||||
if(int_member <= 10) {
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct B {
|
||||
B(const int n = 0) {
|
||||
if(n <= 10) {}
|
||||
}
|
||||
};
|
||||
|
||||
const volatile int volatile_const_global = 0;
|
||||
|
||||
void test1() {
|
||||
func_with_default_arg(100);
|
||||
|
||||
A a(100);
|
||||
if(a.int_member <= 10) {}
|
||||
|
||||
if(volatile_const_global <= 10) {}
|
||||
}
|
||||
@@ -112,4 +112,29 @@ void myFunction() {
|
||||
assert(CHECK_RANGE(ui, 0, 10)); // reasonable use
|
||||
assert(UI >= ZERO); // violation (not detected)
|
||||
assert(ui GE 0); // violation
|
||||
|
||||
if ((unsigned char)si >= 0) { // violation
|
||||
}
|
||||
if ((unsigned char)(signed int)si >= 0) { // violation
|
||||
}
|
||||
if ((signed int)(unsigned char)si >= 0) { // violation
|
||||
}
|
||||
if ((unsigned char)(signed char)si >= 0) { // violation
|
||||
}
|
||||
if ((signed char)(unsigned char)si >= 0) {
|
||||
}
|
||||
|
||||
if ((signed int)(unsigned char)(signed int)si >= 0) { // violation
|
||||
}
|
||||
if ((signed char)(unsigned char)(signed int)si >= 0) {
|
||||
}
|
||||
if ((signed int)(unsigned char)(signed char)si >= 0) { // violation
|
||||
}
|
||||
|
||||
if (ui <= 0) {
|
||||
}
|
||||
if (0 <= ui) { // violation
|
||||
}
|
||||
if (0 < ui) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,4 +112,29 @@ void myFunction() {
|
||||
assert(CHECK_RANGE(ui, 0, 10)); // reasonable use
|
||||
assert(UI >= ZERO); // violation (not detected)
|
||||
assert(ui GE 0); // violation
|
||||
|
||||
if ((unsigned char)si >= 0) { // violation
|
||||
}
|
||||
if ((unsigned char)(signed int)si >= 0) { // violation
|
||||
}
|
||||
if ((signed int)(unsigned char)si >= 0) { // violation
|
||||
}
|
||||
if ((unsigned char)(signed char)si >= 0) { // violation
|
||||
}
|
||||
if ((signed char)(unsigned char)si >= 0) {
|
||||
}
|
||||
|
||||
if ((signed int)(unsigned char)(signed int)si >= 0) { // violation
|
||||
}
|
||||
if ((signed char)(unsigned char)(signed int)si >= 0) {
|
||||
}
|
||||
if ((signed int)(unsigned char)(signed char)si >= 0) { // violation
|
||||
}
|
||||
|
||||
if (ui <= 0) {
|
||||
}
|
||||
if (0 <= ui) { // violation
|
||||
}
|
||||
if (0 < ui) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
| UnsignedGEZero.c:101:9:101:15 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.c:111:9:111:15 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.c:114:9:114:15 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.c:116:6:116:27 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.c:118:6:118:39 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.c:120:6:120:39 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.c:122:6:122:40 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.c:127:6:127:51 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.c:131:6:131:52 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.c:136:6:136:12 | ... <= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:40:6:40:12 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:48:6:48:15 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:54:6:54:12 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
@@ -29,3 +36,10 @@
|
||||
| UnsignedGEZero.cpp:101:9:101:15 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:111:9:111:15 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:114:9:114:15 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:116:6:116:27 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:118:6:118:39 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:120:6:120:39 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:122:6:122:40 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:127:6:127:51 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:131:6:131:52 | ... >= ... | Pointless comparison of unsigned value to zero. |
|
||||
| UnsignedGEZero.cpp:136:6:136:12 | ... <= ... | Pointless comparison of unsigned value to zero. |
|
||||
|
||||
2036
cpp/upgrades/c9ac0461491edef3b1ab79f03d007a47522dda90/old.dbscheme
Normal file
2036
cpp/upgrades/c9ac0461491edef3b1ab79f03d007a47522dda90/old.dbscheme
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: Add support for C++'s <=> operator
|
||||
compatibility: backwards
|
||||
@@ -50,7 +50,7 @@ namespace Semmle.Autobuild
|
||||
|
||||
var build = GetBuildScript(builder, dotNetPath, environment, compatibleClr, projectOrSolution.FullPath);
|
||||
|
||||
ret &= clean & BuildScript.Try(restore) & build;
|
||||
ret &= BuildScript.Try(clean) & BuildScript.Try(restore) & build;
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
@@ -1,41 +1,7 @@
|
||||
# Experimental CodeQL queries and libraries
|
||||
|
||||
In addition to our standard CodeQL queries and libraries, this repository may also contain queries and libraries of a more experimental nature. Experimental queries and libraries can be improved incrementally and may eventually reach a sufficient maturity to be included in our standard libraries and queries.
|
||||
In addition to [our supported queries and libraries](supported-queries.md), this repository also contains queries and libraries of a more experimental nature. Experimental queries and libraries can be improved incrementally and may eventually reach a sufficient maturity to be included in our supported queries and libraries.
|
||||
|
||||
Experimental queries and libraries may not be actively maintained as the standard libraries evolve. They may also be changed in backwards-incompatible ways or may be removed entirely in the future without deprecation warnings.
|
||||
Experimental queries and libraries may not be actively maintained as the [supported](supported-queries.md) libraries evolve. They may also be changed in backwards-incompatible ways or may be removed entirely in the future without deprecation warnings.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. **Directory structure**
|
||||
|
||||
- Experimental queries and libraries are stored in the `experimental` subdirectory within each language-specific directory in the [CodeQL repository](https://github.com/Semmle/ql). For example, experimental Java queries and libraries are stored in `ql/java/ql/src/experimental` and any corresponding tests in `ql/java/ql/test/experimental`.
|
||||
- The structure of an `experimental` subdirectory mirrors the structure of standard queries and libraries (or tests) in the parent directory.
|
||||
|
||||
2. **Query metadata**
|
||||
|
||||
- The query `@id` must not clash with any other queries in the repository.
|
||||
- The query must have a `@name` and `@description` to explain its purpose.
|
||||
- The query must have a `@kind` and `@problem.severity` as required by CodeQL tools.
|
||||
|
||||
For details, see the [guide on query metadata](https://github.com/Semmle/ql/blob/master/docs/query-metadata-style-guide.md).
|
||||
|
||||
3. **Formatting**
|
||||
|
||||
- The queries and libraries must be [autoformatted](https://help.semmle.com/codeql/codeql-for-vscode/reference/editor.html#autoformatting).
|
||||
|
||||
4. **Compilation**
|
||||
|
||||
- Compilation of the query and any associated libraries and tests must be resilient to future development of the standard libraries. This means that the functionality cannot use internal APIs, cannot depend on the output of `getAQlClass`, and cannot make use of regexp matching on `toString`.
|
||||
- The query and any associated libraries and tests must not cause any compiler warnings to be emitted (such as use of deprecated functionality or missing `override` annotations).
|
||||
|
||||
5. **Results**
|
||||
|
||||
- The query must have at least one true positive result on some revision of a real project.
|
||||
|
||||
6. **Contributor License Agreement**
|
||||
|
||||
- The contributor can satisfy the [CLA](CONTRIBUTING.md#contributor-license-agreement).
|
||||
|
||||
## Non-requirements
|
||||
|
||||
Other criteria typically required for our standard queries and libraries are not required for experimental queries and libraries. In particular, fully disciplined query [metadata](docs/query-metadata-style-guide.md), query [help](docs/query-help-style-guide.md), tests, a low false positive rate and performance tuning are not required (but nonetheless recommended).
|
||||
See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines on submitting a new experimental query.
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div id="siteBanner">
|
||||
<div class="textContainer">
|
||||
<div class="logocontainer">
|
||||
<a href="https://semmle.com/" id="Header-logo" class="">
|
||||
<a href="https://help.semmle.com/" id="Header-logo" class="">
|
||||
<svg class="Header-logo-white" width="98" height="20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<path id="a" d="M0 .149h12.872v18.814H0z"></path>
|
||||
@@ -102,7 +102,7 @@
|
||||
{{super()}}
|
||||
</div>
|
||||
<div class="privacy">
|
||||
<a target="_blank" href="https://semmle.com/privacy-policy" alt="Privacy policy and tracking preferences" title="Privacy policy and tracking preferences">Privacy policy</a>
|
||||
<a target="_blank" href="https://help.semmle.com/privacy-policy.html" alt="Privacy policy and tracking preferences" title="Privacy policy and tracking preferences">Privacy policy</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -61,7 +61,7 @@ These topics are discussed in detail in the `QL language handbook <https://help.
|
||||
References
|
||||
----------
|
||||
|
||||
Academic references available from the `Semmle website <https://semmle.com/publications>`__ also provide an overview of QL and its semantics. Other useful references on database query languages and Datalog:
|
||||
Academic references available from the `Semmle website <https://help.semmle.com/publications.html>`__ also provide an overview of QL and its semantics. Other useful references on database query languages and Datalog:
|
||||
|
||||
- `Database theory: Query languages <http://www.lsv.ens-cachan.fr/~segoufin/Papers/Mypapers/DB-chapter.pdf>`__
|
||||
- `Logic Programming and Databases book - Amazon page <http://www.amazon.co.uk/Programming-Databases-Surveys-Computer-Science/dp/3642839541>`__
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
Introducing the CodeQL libraries for COBOL
|
||||
==========================================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
There is an extensive library for analyzing COBOL code. The classes in this library present the data from a CodeQL database in an object-oriented form and provide abstractions and predicates to help you with common analysis tasks.
|
||||
|
||||
The library is implemented as a set of QL modules–that is, files with the extension ``.qll``. The module ``cobol.qll`` imports most other standard library modules, so you can include the complete library by beginning your query with:
|
||||
|
||||
.. code-block:: ql
|
||||
|
||||
import cobol
|
||||
|
||||
The rest of this tutorial briefly summarizes the most important classes and predicates provided by this library, including references to the `detailed API documentation <https://help.semmle.com/qldoc/cobol/>`__ where applicable.
|
||||
|
||||
Introducing the library
|
||||
-----------------------
|
||||
|
||||
The CodeQL library for COBOL presents information about COBOL source code at different levels:
|
||||
|
||||
- **Textual** — classes that represent source code as unstructured text files
|
||||
- **Lexical** — classes that represent comments and other tokens of interest
|
||||
- **Syntactic** — classes that represent source code as an abstract syntax tree
|
||||
- **Name binding** — classes that represent data entries and data references
|
||||
- **Control flow** — classes that represent the flow of control during execution
|
||||
- **Frameworks** — classes that represent interactions via CICS and SQL
|
||||
|
||||
Note that representations above the textual level (for example the lexical representation or the flow graphs) are only available for COBOL code that does not contain fatal syntax errors. For code with such errors, the only information available is at the textual level, as well as information about the errors themselves.
|
||||
|
||||
Textual level
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
At its most basic level, a COBOL code base can simply be viewed as a collection of files organized into folders.
|
||||
|
||||
Files and folders
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Files are represented as entities of class `File <https://help.semmle.com/qldoc/cobol/semmle/cobol/Files.qll/type.Files$File.html>`__, and folders as entities of class `Folder <https://help.semmle.com/qldoc/cobol/semmle/cobol/Files.qll/type.Files$Folder.html>`__, both of which are subclasses of class `Container <https://help.semmle.com/qldoc/cobol/semmle/cobol/Files.qll/type.Files$Container.html>`__.
|
||||
|
||||
Class `Container <https://help.semmle.com/qldoc/cobol/semmle/cobol/Files.qll/type.Files$Container.html>`__ provides the following member predicates:
|
||||
|
||||
- ``Container.getParentContainer()`` returns the parent folder of the file or folder.
|
||||
- ``Container.getAFile()`` returns a file within the folder.
|
||||
- ``Container.getAFolder()`` returns a folder nested within the folder.
|
||||
|
||||
Note that while ``getAFile`` and ``getAFolder`` are declared on class `Container <https://help.semmle.com/qldoc/cobol/semmle/cobol/Files.qll/type.Files$Container.html>`__, they currently only have results for `Folder <https://help.semmle.com/qldoc/cobol/semmle/cobol/Files.qll/type.Files$Folder.html>`__\ s.
|
||||
|
||||
Both files and folders have paths, which can be accessed by the predicate ``Container.getAbsolutePath()``. For example, if ``f`` represents a file with the path ``/home/user/project/src/main.cbl``, then ``f.getAbsolutePath()`` evaluates to the string ``"/home/user/project/src/main.cbl"``, while ``f.getParentContainer().getAbsolutePath()`` returns ``"/home/user/project/src"``.
|
||||
|
||||
These paths are absolute file system paths. If you want to obtain the path of a file relative to the source location in the CodeQL database, use ``Container.getRelativePath()`` instead. Note, however, that a database may contain files that are not located underneath the source location; for such files, ``getRelativePath()`` will not return anything.
|
||||
|
||||
The following member predicates of class `Container <https://help.semmle.com/qldoc/cobol/semmle/cobol/Files.qll/type.Files$Container.html>`__ provide more information about the name of a file or folder:
|
||||
|
||||
- ``Container.getBaseName()`` returns the base name of a file or folder, not including its parent folder, but including its extension. In the above example, ``f.getBaseName()`` would return the string ``"main.cbl"``.
|
||||
- ``Container.getStem()`` is similar to ``Container.getBaseName()``, but it does *not* include the file extension; so ``f.getStem()`` returns ``"main"``.
|
||||
- ``Container.getExtension()`` returns the file extension, not including the dot; so ``f.getExtension()`` returns ``"cbl"``.
|
||||
|
||||
For example, the following query computes, for each folder, the number of COBOL files (that is, files with extension ``cbl``) contained in the folder:
|
||||
|
||||
.. code-block:: ql
|
||||
|
||||
import cobol
|
||||
|
||||
from Folder d
|
||||
select d.getRelativePath(), count(File f | f = d.getAFile() and f.getExtension() = "cbl")
|
||||
|
||||
Locations
|
||||
^^^^^^^^^
|
||||
|
||||
Most entities in a CodeQL database have an associated source location. Locations are identified by four pieces of information: a file, a start line, a start column, an end line, and an end column. Line and column counts are 1-based (so the first character of a file is at line 1, column 1), and the end position is inclusive.
|
||||
|
||||
All entities associated with a source location belong to the class `Locatable <https://help.semmle.com/qldoc/cobol/semmle/cobol/Location.qll/type.Location$Locatable.html>`__. The location itself is modeled by the class `Location <https://help.semmle.com/qldoc/cobol/semmle/cobol/Location.qll/type.Location$Location.html>`__ and can be accessed through the member predicate ``Locatable.getLocation()``. The `Location <https://help.semmle.com/qldoc/cobol/semmle/cobol/Location.qll/type.Location$Location.html>`__ class provides the following member predicates:
|
||||
|
||||
- ``Location.getFile()``, ``Location.getStartLine()``, ``Location.getStartColumn()``, ``Location.getEndLine()``, ``Location.getEndColumn()`` return detailed information about the location.
|
||||
- ``Location.getNumLines()`` returns the number of (whole or partial) lines covered by the location.
|
||||
- ``Location.startsBefore(Location)`` and ``Location.endsAfter(Location)`` determine whether one location starts before or ends after another location.
|
||||
- ``Location.contains(Location)`` indicates whether one location completely contains another location; ``l1.contains(l2)`` holds if, and only if, ``l1.startsBefore(l2)`` and ``l1.endsAfter(l2)``.
|
||||
|
||||
Lexical level
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
At this level we represent comments through the `Comment <https://help.semmle.com/qldoc/cobol/semmle/cobol/Comments.qll/type.Comments$Comment.html>`__ class. We do not currently retain any tokens other than scope terminators (for example ``END-IF``), which are represented by the `ScopeTerminator <https://help.semmle.com/qldoc/cobol/semmle/cobol/Stmts.qll/type.Stmts$ScopeTerminator.html>`__ class.
|
||||
|
||||
Comments
|
||||
^^^^^^^^
|
||||
|
||||
The class `Comment <https://help.semmle.com/qldoc/cobol/semmle/cobol/Comments.qll/type.Comments$Comment.html>`__ represents the comments that occur in COBOL programs:
|
||||
|
||||
The most important member predicates are as follows:
|
||||
|
||||
- ``Comment.getText()`` returns the source text of the comment, not including delimiters.
|
||||
- ``Comment.getScope()`` returns the location of the source code to which the comment is bound.
|
||||
|
||||
Scope terminators
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The class `ScopeTerminator <https://help.semmle.com/qldoc/cobol/semmle/cobol/Stmts.qll/type.Stmts$ScopeTerminator.html>`__ represents the scope terminators that occur in COBOL programs:
|
||||
|
||||
The most important member predicates are as follows:
|
||||
|
||||
- ``ScopeTerminator.getStmt()`` returns the statement whose scope this terminator is closing.
|
||||
|
||||
Syntactic level
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The majority of classes in the CodeQL library for COBOL are concerned with representing a COBOL program as a collection of `abstract syntax trees <http://en.wikipedia.org/wiki/Abstract_syntax_tree>`__ (ASTs).
|
||||
|
||||
The class `ASTNode <https://help.semmle.com/qldoc/cobol/semmle/cobol/AstNode.qll/type.AstNode$AstNode.html>`__ contains all entities representing nodes in the abstract syntax trees and defines generic tree traversal predicates:
|
||||
|
||||
- ``ASTNode.getParent()``: returns the parent node of this AST node, if any.
|
||||
|
||||
Please note that the libraries for COBOL do not currently represent all possible parts of a COBOL program. Due to the complexity of the language, and its many dialects, this is an ongoing task. We prioritize elements that are of interest to queries, and expand this selection over time. Please check the `detailed API documentation <https://help.semmle.com/qldoc/cobol/>`__ to see what is currently available.
|
||||
|
||||
The main structure of any COBOL program is represented by the `Unit <https://help.semmle.com/qldoc/cobol/semmle/cobol/Units.qll/type.Units$Unit.html>`__ class and its subclasses. For example, each program definition has a `ProgramDefinition <https://help.semmle.com/qldoc/cobol/semmle/cobol/Units.qll/type.Units$ProgramDefinition.html>`__ counterpart. For each ``PROCEDURE DIVISION`` in the program, there will be a `ProcedureDivision <https://help.semmle.com/qldoc/cobol/semmle/cobol/AST_extended.qll/type.AST_extended$ProcedureDivision.html>`__ class.
|
||||
|
||||
All data definitions are made accessible through the `DescriptionEntry <https://help.semmle.com/qldoc/cobol/semmle/cobol/DataEntries.qll/type.DataEntries$DescriptionEntry.html>`__ class and its subclasses. In particular, you can use `DataDescriptionEntry <https://help.semmle.com/qldoc/cobol/semmle/cobol/DataEntries.qll/type.DataEntries$DataDescriptionEntry.html>`__ to find the typical data entries defined in a ``WORKING-STORAGE SECTION``.
|
||||
|
||||
References to data items are modeled through the `DataReference <https://help.semmle.com/qldoc/cobol/semmle/cobol/References.qll/type.References$DataReference.html>`__ class. You can use ``DataReference.getTarget()`` to resolve the reference to the matching data item.
|
||||
|
||||
Individual statements are represented by the class `Stmt <https://help.semmle.com/qldoc/cobol/semmle/cobol/Stmts.qll/type.Stmts$Stmt.html>`__ and its subclasses. The name of the specific type starts with the statement's verb. For example, ``OPEN`` statements are covered by the class `Open <https://help.semmle.com/qldoc/cobol/semmle/cobol/Stmts.qll/type.Stmts$Open.html>`__. Unknown statement types are covered by the
|
||||
`OtherStmt <https://help.semmle.com/qldoc/cobol/semmle/cobol/AST_extended.qll/type.AST_extended$OtherStmt.html>`__ class.
|
||||
|
||||
Control flow
|
||||
~~~~~~~~~~~~
|
||||
|
||||
You can represent a program in terms of its control flow graph (CFG) using the ``AstNode.getASuccessor`` predicate. You can use this predicate to find possible successors to any statement, sentence, or unit in a procedure division.
|
||||
|
||||
Parse errors
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
COBOL code that contains breaking syntax errors cannot usually be analyzed. All that is available in this case is a value of class `Error <https://help.semmle.com/qldoc/cobol/semmle/cobol/Errors.qll/type.Errors$Error.html>`__ representing the parse error. It provides information about the syntax error location and the error message through predicates ``Error.getLocation`` and ``Error.getMessage`` respectively.
|
||||
|
||||
Frameworks
|
||||
~~~~~~~~~~
|
||||
|
||||
CICS
|
||||
^^^^
|
||||
|
||||
Calls to the CICS system through ``EXEC CICS`` are represented by the class `CICS <https://help.semmle.com/qldoc/cobol/semmle/cobol/AST_extended.qll/type.AST_extended$Cics.html>`__.
|
||||
|
||||
SQL
|
||||
^^^
|
||||
|
||||
Calls to the SQL system through ``EXEC SQL`` are represented by the class
|
||||
`SqlStmt <https://help.semmle.com/qldoc/cobol/semmle/cobol/Sql.qll/type.Sql$SqlStmt.html>`__ and its subclasses.
|
||||
|
||||
What next?
|
||||
----------
|
||||
|
||||
- Find out more about QL in the `QL language handbook <https://help.semmle.com/QL/ql-handbook/index.html>`__ and `QL language specification <https://help.semmle.com/QL/ql-spec/language.html>`__.
|
||||
@@ -1,20 +0,0 @@
|
||||
CodeQL for COBOL
|
||||
================
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:hidden:
|
||||
|
||||
introduce-libraries-cobol
|
||||
|
||||
.. include:: ../../support/cobol-note.rst
|
||||
|
||||
This page provides an overview of the CodeQL for COBOL documentation that is currently available.
|
||||
|
||||
- :doc:`Introducing the CodeQL libraries for COBOL <introduce-libraries-cobol>` introduces the standard libraries used to write queries for COBOL code.
|
||||
|
||||
|
||||
Other resources
|
||||
---------------
|
||||
|
||||
- For more information about the library for COBOL see the `CodeQL library for COBOL <https://help.semmle.com/qldoc/cobol/>`__.
|
||||
@@ -67,7 +67,6 @@ For more information on using CodeQL to query code written in a specific languag
|
||||
|
||||
cpp/ql-for-cpp
|
||||
csharp/ql-for-csharp
|
||||
cobol/ql-for-cobol
|
||||
go/ql-for-go
|
||||
java/ql-for-java
|
||||
javascript/ql-for-javascript
|
||||
|
||||
@@ -78,7 +78,7 @@ When writing your own alert queries, you would typically import the standard lib
|
||||
|
||||
- C/C++: ``cpp``
|
||||
- C#: ``csharp``
|
||||
- COBOL: ``cobol``
|
||||
- Go: ``go``
|
||||
- Java: ``java``
|
||||
- JavaScript/TypeScript: ``javascript``
|
||||
- Python: ``python``
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
.. pull-quote:: Important
|
||||
|
||||
CodeQL for COBOL is being deprecated after the 1.23 release of CodeQL.
|
||||
Future releases, starting with 1.24, will no longer contain support for analyzing COBOL source code.
|
||||
We are not aware of any customers who will be affected by this change. If you do have any concerns, please contact your account manager.
|
||||
@@ -80,4 +80,4 @@ htmlhelp_basename = 'Supported languages and frameworks'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['read-me-project.rst', 'cobol-note.rst']
|
||||
exclude_patterns = ['read-me-project.rst']
|
||||
@@ -10,8 +10,6 @@ Customers with any questions should contact their usual Semmle contact with any
|
||||
If you're not a customer yet, contact us at info@semmle.com
|
||||
with any questions you have about language and compiler support.
|
||||
|
||||
.. include:: cobol-note.rst
|
||||
|
||||
.. csv-table::
|
||||
:file: versions-compilers.csv
|
||||
:header-rows: 1
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Language,Variants,Compilers,Extensions
|
||||
C/C++,"C89, C99, C11, C++98, C++03, C++11, C++14, C++17","Clang (and clang-cl [1]_) extensions (up to Clang 8.0),
|
||||
C/C++,"C89, C99, C11, C18, C++98, C++03, C++11, C++14, C++17","Clang (and clang-cl [1]_) extensions (up to Clang 9.0),
|
||||
|
||||
GNU extensions (up to GCC 8.3),
|
||||
GNU extensions (up to GCC 9.2),
|
||||
|
||||
Microsoft extensions (up to VS 2019),
|
||||
|
||||
Arm Compiler 5.0 [2]_","``.cpp``, ``.c++``, ``.cxx``, ``.hpp``, ``.hh``, ``.h++``, ``.hxx``, ``.c``, ``.cc``, ``.h``"
|
||||
Arm Compiler 5 [2]_","``.cpp``, ``.c++``, ``.cxx``, ``.hpp``, ``.hh``, ``.h++``, ``.hxx``, ``.c``, ``.cc``, ``.h``"
|
||||
C#,C# up to 8.0. with .NET up to 4.8 [3]_,"Microsoft Visual Studio up to 2019,
|
||||
|
||||
.NET Core up to 3.0","``.sln``, ``.csproj``, ``.cs``, ``.cshtml``, ``.xaml``"
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
This document outlines the structure of Semmle query files. You should adopt this structure when contributing custom queries to this repository, in order to ensure that new queries are consistent with the standard Semmle queries.
|
||||
This document outlines the structure of CodeQL query files. You should adopt this structure when contributing custom queries to this repository, in order to ensure that new queries are consistent with the standard CodeQL queries.
|
||||
|
||||
## Query files (.ql extension)
|
||||
|
||||
@@ -67,11 +67,11 @@ You must define an `@description` property for your query. This property defines
|
||||
|
||||
### Query ID `@id`
|
||||
|
||||
You must specify an `@id` property for your query. It must be unique in the Semmle namespace and should follow the standard Semmle convention. That is, it should begin with the 'language code' for the language that the query analyzes followed by a forward slash. The following language codes are supported:
|
||||
You must specify an `@id` property for your query. It must be unique and should follow the standard CodeQL convention. That is, it should begin with the 'language code' for the language that the query analyzes followed by a forward slash. The following language codes are supported:
|
||||
|
||||
* C and C++: `cpp`
|
||||
* C#: `cs`
|
||||
* COBOL: `cobol`
|
||||
* Go: `go`
|
||||
* Java: `java`
|
||||
* JavaScript and TypeScript: `js`
|
||||
* Python: `py`
|
||||
@@ -105,7 +105,7 @@ Note, `@id` properties should be consistent for queries that highlight the same
|
||||
* alerts (`@kind problem`)
|
||||
* alerts containing path information (`@kind path-problem`)
|
||||
|
||||
Alert queries (`@kind problem` or `path-problem`) support two further properties. These are added by Semmle after the query has been tested, prior to deployment to LGTM. The following information is for reference:
|
||||
Alert queries (`@kind problem` or `path-problem`) support two further properties. These are added by GitHub staff after the query has been tested, prior to deployment to LGTM. The following information is for reference:
|
||||
|
||||
|
||||
|
||||
|
||||
79
docs/supported-queries.md
Normal file
79
docs/supported-queries.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Supported CodeQL queries and libraries
|
||||
|
||||
Queries and libraries outside [the `experimental` directories](experimental.md) are _supported_ by GitHub, allowing our users to rely on their continued existence and functionality in the future:
|
||||
|
||||
1. Once a query or library has appeared in a stable release, a one-year deprecation period is required before we can remove it. There can be exceptions to this when it's not technically possible to mark it as deprecated.
|
||||
2. Major changes to supported queries and libraries are always announced in the [change notes for stable releases](../change-notes/).
|
||||
3. We will do our best to address user reports of false positives or false negatives.
|
||||
|
||||
Because of these commitments, we set a high bar for accepting new supported queries. The requirements are detailed in the rest of this document.
|
||||
|
||||
## Steps for introducing a new supported query
|
||||
|
||||
The process must begin with the first step and must conclude with the final step. The remaining steps can be performed in any order.
|
||||
|
||||
1. **Have the query merged into the appropriate `experimental` subdirectory**
|
||||
|
||||
See [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
2. **Write a query help file**
|
||||
|
||||
Query help files explain the purpose of your query to other users. Write your query help in a `.qhelp` file and save it in the same directory as your query. For more information on writing query help, see the [Query help style guide](query-help-style-guide.md).
|
||||
|
||||
- Note, in particular, that almost all queries need to have a pair of "before" and "after" examples demonstrating the kind of problem the query identifies and how to fix it. Make sure that the examples are actually consistent with what the query does, for example by including them in your unit tests.
|
||||
- At the time of writing, there is no way of previewing help locally. Once you've opened a PR, a preview will be created as part of the CI checks. A GitHub employee will review this and let you know of any problems.
|
||||
|
||||
3. **Write unit tests**
|
||||
|
||||
Add one or more unit tests for the query (and for any library changes you make) to the `ql/<language>/ql/test/experimental` directory. Tests for library changes go into the `library-tests` subdirectory, and tests for queries go into `query-tests` with their relative path mirroring the query's location under `ql/<language>/ql/src/experimental`.
|
||||
|
||||
See the section on [Testing custom queries](https://help.semmle.com/codeql/codeql-cli/procedures/test-queries.html) in the [CodeQL documentation](https://help.semmle.com/codeql/) for more information.
|
||||
|
||||
4. **Test for correctness on real-world code**
|
||||
|
||||
Test the query on a number of large real-world projects to make sure it doesn't give too many false positive results. Adjust the `@precision` and `@problem.severity` attributes in accordance with the real-world results you observe. See the advice on query metadata below.
|
||||
|
||||
You can use the LGTM.com [query console](https://lgtm.com/query) to get an overview of true and false positive results on a large number of projects. The simplest way to do this is to:
|
||||
|
||||
1. [Create a list of prominent projects](https://lgtm.com/help/lgtm/managing-project-lists) on LGTM.
|
||||
2. In the query console, [run your query against your custom project list](https://lgtm.com/help/lgtm/using-query-console).
|
||||
3. Save links to your query console results and include them in discussions on issues and pull requests.
|
||||
|
||||
5. **Test and improve performance**
|
||||
|
||||
There must be a balance between the execution time of a query and the value of its results: queries that are highly valuable and broadly applicable can be allowed to take longer to run. In all cases, you need to address any easy-to-fix performance issues before the query is put into production.
|
||||
|
||||
QL performance profiling and tuning is an advanced topic, and some tasks will require assistance from GitHub employees. With that said, there are several things you can do.
|
||||
|
||||
- Understand [the evaluation model of QL](https://help.semmle.com/QL/ql-handbook/evaluation.html). It's more similar to SQL than to any mainstream programming language.
|
||||
- Most performance tuning in QL boils down to computing as few tuples (rows of data) as possible. As a mental model, think of predicate evaluation as enumerating all combinations of parameters that satisfy the predicate body. This includes the implicit parameters `this` and `result`.
|
||||
- The major libraries in CodeQL are _cached_ and will only be computed once for the entire suite of queries. The first query that needs a cached _stage_ will trigger its evaluation. This means that query authors should usually only look at the run time of the last stage of evaluation.
|
||||
- In [the settings for the VSCode extension](https://help.semmle.com/codeql/codeql-for-vscode/reference/settings.html), check the box "Running Queries: Debug" (`codeQL.runningQueries.debug`). Then find "CodeQL Query Server" in the VSCode Output panel (View -> Output) and capture the output when running the query. That output contains timing and tuple counts for all computed predicates.
|
||||
- To clear the entire cache, invoke "CodeQL: Clear Cache" from the VSCode command palette.
|
||||
|
||||
6. **Make sure your query has the correct metadata**
|
||||
|
||||
For the full reference on writing query metadata, see the [Query metadata style guide](query-metadata-style-guide.md). The following constitutes a checklist.
|
||||
|
||||
a. Each query needs a `@name`, a `@description`, and a `@kind`.
|
||||
|
||||
b. Alert queries also need a `@problem.severity` and a `@precision`.
|
||||
|
||||
- The severity is one of `error`, `warning`, or `recommendation`.
|
||||
- The precision is one of `very-high`, `high`, `medium` or `low`. It may take a few iterations to get this right.
|
||||
- Currently, LGTM runs all `error` or `warning` queries with a `very-high`, `high`, or `medium` precision. In addition, `recommendation` queries with `very-high` or `high` precision are run.
|
||||
- However, results from `error` and `warning` queries with `medium` precision, as well as `recommendation` queries with `high` precision, are not shown by default.
|
||||
|
||||
c. All queries need an `@id`.
|
||||
|
||||
- The ID should be consistent with the ids of similar queries for other languages; for example, there is a C/C++ query looking for comments containing the word "TODO" which has id `cpp/todo-comment`, and its C# counterpart has id `cs/todo-comment`.
|
||||
|
||||
d. Provide one or more `@tags` describing the query.
|
||||
|
||||
- Tags are free-form, but we have some conventions, especially for tagging security queries with corresponding CWE numbers.
|
||||
|
||||
7. **Move your query out of `experimental`**
|
||||
|
||||
- The structure of an `experimental` subdirectory mirrors the structure of its parent directory, so this step may just be a matter of removing the `experimental/` prefix of the query and test paths. Be sure to also edit any references to the query path in tests.
|
||||
- Add the query to one of the legacy suite files in `ql/<language>/config/suites/<language>/` if it exists. Note that there are separate suite directories for C and C++, `c` and `cpp` respectively, and the query should be added to one or both as appropriate.
|
||||
- Add a release note to `change-notes/<next-version>/analysis-<language>.md`.
|
||||
4
java/ql/src/experimental/CWE-094/ScriptEngine.java
Normal file
4
java/ql/src/experimental/CWE-094/ScriptEngine.java
Normal file
@@ -0,0 +1,4 @@
|
||||
// Bad: ScriptEngine allows arbitrary code injection
|
||||
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
|
||||
ScriptEngine scriptEngine = scriptEngineManager.getEngineByExtension("js");
|
||||
Object result = scriptEngine.eval(code);
|
||||
25
java/ql/src/experimental/CWE-094/ScriptEngine.qhelp
Normal file
25
java/ql/src/experimental/CWE-094/ScriptEngine.qhelp
Normal file
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>The ScriptEngine API has been available since the release of Java 6.
|
||||
It allows applications to interact with scripts written in languages such as JavaScript.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Use "Cloudbees Rhino Sandbox" or sandboxing with SecurityManager or use <a href="https://www.graalvm.org/">graalvm</a> instead.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following code could execute random JavaScript code</p>
|
||||
<sample src="ScriptEngine.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
CERT coding standard: <a href="https://wiki.sei.cmu.edu/confluence/display/java/IDS52-J.+Prevent+code+injection">ScriptEngine code injection</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
51
java/ql/src/experimental/CWE-094/ScriptEngine.ql
Normal file
51
java/ql/src/experimental/CWE-094/ScriptEngine.ql
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @name ScriptEngine evaluation
|
||||
* @description Malicious Javascript code could cause arbitrary command execution at the OS level
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/unsafe-eval
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class ScriptEngineMethod extends Method {
|
||||
ScriptEngineMethod() {
|
||||
this.getDeclaringType().hasQualifiedName("javax.script", "ScriptEngine") and
|
||||
this.hasName("eval")
|
||||
}
|
||||
}
|
||||
|
||||
predicate scriptEngine(MethodAccess ma, Expr sink) {
|
||||
exists(Method m | m = ma.getMethod() |
|
||||
m instanceof ScriptEngineMethod and
|
||||
sink = ma.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
class ScriptEngineSink extends DataFlow::ExprNode {
|
||||
ScriptEngineSink() { scriptEngine(_, this.getExpr()) }
|
||||
|
||||
MethodAccess getMethodAccess() { scriptEngine(result, this.getExpr()) }
|
||||
}
|
||||
|
||||
class ScriptEngineConfiguration extends TaintTracking::Configuration {
|
||||
ScriptEngineConfiguration() { this = "ScriptEngineConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source instanceof RemoteFlowSource
|
||||
or
|
||||
source instanceof LocalUserInput
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof ScriptEngineSink }
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptEngineConfiguration conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode().(ScriptEngineSink).getMethodAccess(), source, sink, "ScriptEngine eval $@.",
|
||||
source.getNode(), "user input"
|
||||
73
java/ql/src/experimental/CWE-643/XPathInjection.java
Normal file
73
java/ql/src/experimental/CWE-643/XPathInjection.java
Normal file
@@ -0,0 +1,73 @@
|
||||
final String xmlStr = "<users>" +
|
||||
" <user name=\"aaa\" pass=\"pass1\"></user>" +
|
||||
" <user name=\"bbb\" pass=\"pass2\"></user>" +
|
||||
"</users>";
|
||||
try {
|
||||
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
|
||||
domFactory.setNamespaceAware(true);
|
||||
DocumentBuilder builder = domFactory.newDocumentBuilder();
|
||||
//Document doc = builder.parse("user.xml");
|
||||
Document doc = builder.parse(new InputSource(new StringReader(xmlStr)));
|
||||
|
||||
XPathFactory factory = XPathFactory.newInstance();
|
||||
XPath xpath = factory.newXPath();
|
||||
|
||||
// Injectable data
|
||||
String user = request.getParameter("user");
|
||||
String pass = request.getParameter("pass");
|
||||
if (user != null && pass != null) {
|
||||
boolean isExist = false;
|
||||
|
||||
// Bad expression
|
||||
String expression1 = "/users/user[@name='" + user + "' and @pass='" + pass + "']";
|
||||
isExist = (boolean)xpath.evaluate(expression1, doc, XPathConstants.BOOLEAN);
|
||||
System.out.println(isExist);
|
||||
|
||||
// Bad expression
|
||||
XPathExpression expression2 = xpath.compile("/users/user[@name='" + user + "' and @pass='" + pass + "']");
|
||||
isExist = (boolean)expression2.evaluate(doc, XPathConstants.BOOLEAN);
|
||||
System.out.println(isExist);
|
||||
|
||||
// Bad expression
|
||||
StringBuffer sb = new StringBuffer("/users/user[@name=");
|
||||
sb.append(user);
|
||||
sb.append("' and @pass='");
|
||||
sb.append(pass);
|
||||
sb.append("']");
|
||||
String query = sb.toString();
|
||||
XPathExpression expression3 = xpath.compile(query);
|
||||
isExist = (boolean)expression3.evaluate(doc, XPathConstants.BOOLEAN);
|
||||
System.out.println(isExist);
|
||||
|
||||
// Good expression
|
||||
String expression4 = "/users/user[@name=$user and @pass=$pass]";
|
||||
xpath.setXPathVariableResolver(v -> {
|
||||
switch (v.getLocalPart()) {
|
||||
case "user":
|
||||
return user;
|
||||
case "pass":
|
||||
return pass;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
});
|
||||
isExist = (boolean)xpath.evaluate(expression4, doc, XPathConstants.BOOLEAN);
|
||||
System.out.println(isExist);
|
||||
|
||||
|
||||
// Bad Dom4j
|
||||
org.dom4j.io.SAXReader reader = new org.dom4j.io.SAXReader();
|
||||
org.dom4j.Document document = reader.read(new InputSource(new StringReader(xmlStr)));
|
||||
isExist = document.selectSingleNode("/users/user[@name='" + user + "' and @pass='" + pass + "']").hasContent();
|
||||
// or document.selectNodes
|
||||
System.out.println(isExist);
|
||||
}
|
||||
} catch (ParserConfigurationException e) {
|
||||
|
||||
} catch (SAXException e) {
|
||||
|
||||
} catch (XPathExpressionException e) {
|
||||
|
||||
} catch (org.dom4j.DocumentException e) {
|
||||
|
||||
}
|
||||
44
java/ql/src/experimental/CWE-643/XPathInjection.qhelp
Normal file
44
java/ql/src/experimental/CWE-643/XPathInjection.qhelp
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
If an XPath expression is built using string concatenation, and the components of the concatenation
|
||||
include user input, a user is likely to be able to create a malicious XPath expression.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
If user input must be included in an XPath expression, pre-compile the query and use variable
|
||||
references to include the user input.
|
||||
</p>
|
||||
<p>
|
||||
XPath injection can also be prevented by using XQuery.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the first, second, and third example, the code accepts a name and password specified by the user, and uses this
|
||||
unvalidated and unsanitized value in an XPath expression. This is vulnerable to the user providing
|
||||
special characters or string sequences that change the meaning of the XPath expression to search
|
||||
for different values.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the fourth example, the code utilizes setXPathVariableResolver which prevents XPath Injection.
|
||||
</p>
|
||||
<p>
|
||||
The fifth example is a dom4j XPath injection example.
|
||||
</p>
|
||||
<sample src="XPathInjection.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://www.owasp.org/index.php?title=Testing_for_XPath_Injection_(OTG-INPVAL-010)">Testing for XPath Injection</a>.</li>
|
||||
<li>OWASP: <a href="https://www.owasp.org/index.php/XPATH_Injection">XPath Injection</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
44
java/ql/src/experimental/CWE-643/XPathInjection.ql
Normal file
44
java/ql/src/experimental/CWE-643/XPathInjection.ql
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @name XPath injection
|
||||
* @description Building an XPath expression from user-controlled sources is vulnerable to insertion of
|
||||
* malicious code by the user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/xml/xpath-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-643
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.security.XmlParsers
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class XPathInjectionConfiguration extends TaintTracking::Configuration {
|
||||
XPathInjectionConfiguration() { this = "XPathInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof XPathInjectionSink }
|
||||
}
|
||||
|
||||
class XPathInjectionSink extends DataFlow::ExprNode {
|
||||
XPathInjectionSink() {
|
||||
exists(Method m, MethodAccess ma | ma.getMethod() = m |
|
||||
m.getDeclaringType().hasQualifiedName("javax.xml.xpath", "XPath") and
|
||||
(m.hasName("evaluate") or m.hasName("compile")) and
|
||||
ma.getArgument(0) = this.getExpr()
|
||||
or
|
||||
m.getDeclaringType().hasQualifiedName("org.dom4j", "Node") and
|
||||
(m.hasName("selectNodes") or m.hasName("selectSingleNode")) and
|
||||
ma.getArgument(0) = this.getExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, XPathInjectionConfiguration c
|
||||
where c.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ flows to here and is used in an XPath expression.",
|
||||
source.getNode(), "User-provided value"
|
||||
@@ -115,11 +115,19 @@ private predicate taintPreservingQualifierToMethod(Method m) {
|
||||
or
|
||||
m.(CollectionMethod).hasName("remove") and m.getParameterType(0).(PrimitiveType).hasName("int")
|
||||
or
|
||||
m.(CollectionMethod).hasName("remove") and m.getNumberOfParameters() = 0
|
||||
or
|
||||
m.(CollectionMethod).hasName("subList")
|
||||
or
|
||||
m.(CollectionMethod).hasName("firstElement")
|
||||
or
|
||||
m.(CollectionMethod).hasName("lastElement")
|
||||
or
|
||||
m.(CollectionMethod).hasName("poll")
|
||||
or
|
||||
m.(CollectionMethod).hasName("peek")
|
||||
or
|
||||
m.(CollectionMethod).hasName("element")
|
||||
}
|
||||
|
||||
private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
|
||||
@@ -147,6 +155,8 @@ private predicate taintPreservingArgumentToQualifier(Method method, int arg) {
|
||||
method.(CollectionMethod).hasName("addElement") and arg = 0
|
||||
or
|
||||
method.(CollectionMethod).hasName("set") and arg = 1
|
||||
or
|
||||
method.(CollectionMethod).hasName("offer") and arg = 0
|
||||
}
|
||||
|
||||
private predicate argToQualifierStep(Expr tracked, Expr sink) {
|
||||
|
||||
@@ -70,4 +70,19 @@ public class A extends ArrayList<Long> {
|
||||
int i2 = (Integer)x2;
|
||||
int j2 = i2;
|
||||
}
|
||||
|
||||
public static class C {
|
||||
private Map<String, String> map;
|
||||
public static C empty = new C(Collections.emptyMap());
|
||||
private C(Map<String, String> map) {
|
||||
this.map = map;
|
||||
}
|
||||
public C() {
|
||||
this(new LinkedHashMap<>());
|
||||
}
|
||||
public void put(String k, String v) {
|
||||
map.put(k, v);
|
||||
empty.put(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,4 @@ The extractor consists of a parser for the latest version of ECMAScript, includi
|
||||
|
||||
## License
|
||||
|
||||
Like the LGTM queries, the JavaScript extractor is licensed under [Apache License 2.0](LICENSE) by [Semmle](https://semmle.com). Some code is derived from other projects, whose licenses are noted in other `LICENSE-*.md` files in this folder.
|
||||
Like the LGTM queries, the JavaScript extractor is licensed under [Apache License 2.0](LICENSE) by [GitHub](https://github.com). Some code is derived from other projects, whose licenses are noted in other `LICENSE-*.md` files in this folder.
|
||||
|
||||
@@ -69,8 +69,11 @@ interface PrepareFilesCommand {
|
||||
command: "prepare-files";
|
||||
filenames: string[];
|
||||
}
|
||||
interface GetMetadataCommand {
|
||||
command: "get-metadata";
|
||||
}
|
||||
type Command = ParseCommand | OpenProjectCommand | CloseProjectCommand
|
||||
| GetTypeTableCommand | ResetCommand | QuitCommand | PrepareFilesCommand;
|
||||
| GetTypeTableCommand | ResetCommand | QuitCommand | PrepareFilesCommand | GetMetadataCommand;
|
||||
|
||||
/** The state to be shared between commands. */
|
||||
class State {
|
||||
@@ -91,7 +94,7 @@ const reloadMemoryThresholdMb = getEnvironmentVariable("SEMMLE_TYPESCRIPT_MEMORY
|
||||
/**
|
||||
* Debugging method for finding cycles in the TypeScript AST. Should not be used in production.
|
||||
*
|
||||
* If cycles are found, additional properties should be added to `isBlacklistedProperty`.
|
||||
* If cycles are found, the whitelist in `astProperties` is too permissive.
|
||||
*/
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
function checkCycle(root: any) {
|
||||
@@ -104,7 +107,8 @@ function checkCycle(root: any) {
|
||||
obj.$cycle_visiting = true;
|
||||
for (let k in obj) {
|
||||
if (!obj.hasOwnProperty(k)) continue;
|
||||
if (isBlacklistedProperty(k)) continue;
|
||||
// Ignore numeric and whitelisted properties.
|
||||
if (+k !== +k && !astPropertySet.has(k)) continue;
|
||||
if (k === "$cycle_visiting") continue;
|
||||
let cycle = visit(obj[k]);
|
||||
if (cycle) {
|
||||
@@ -122,30 +126,133 @@ function checkCycle(root: any) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A property that should not be serialized as part of the AST, because they
|
||||
* lead to cycles or are just not needed.
|
||||
*
|
||||
* Because of restrictions on `JSON.stringify`, these properties may also not
|
||||
* be used as part of a command response.
|
||||
*/
|
||||
function isBlacklistedProperty(k: string) {
|
||||
return k === "parent" || k === "pos" || k === "end"
|
||||
|| k === "symbol" || k === "localSymbol"
|
||||
|| k === "flowNode" || k === "returnFlowNode" || k === "endFlowNode" || k === "fallthroughFlowNode"
|
||||
|| k === "nextContainer" || k === "locals"
|
||||
|| k === "bindDiagnostics" || k === "bindSuggestionDiagnostics";
|
||||
}
|
||||
/** Property names to extract from the TypeScript AST. */
|
||||
const astProperties: string[] = [
|
||||
"$declarationKind",
|
||||
"$declaredSignature",
|
||||
"$end",
|
||||
"$lineStarts",
|
||||
"$overloadIndex",
|
||||
"$pos",
|
||||
"$resolvedSignature",
|
||||
"$symbol",
|
||||
"$tokens",
|
||||
"$type",
|
||||
"argument",
|
||||
"argumentExpression",
|
||||
"arguments",
|
||||
"assertsModifier",
|
||||
"asteriskToken",
|
||||
"attributes",
|
||||
"block",
|
||||
"body",
|
||||
"caseBlock",
|
||||
"catchClause",
|
||||
"checkType",
|
||||
"children",
|
||||
"clauses",
|
||||
"closingElement",
|
||||
"closingFragment",
|
||||
"condition",
|
||||
"constraint",
|
||||
"constructor",
|
||||
"declarationList",
|
||||
"declarations",
|
||||
"decorators",
|
||||
"default",
|
||||
"delete",
|
||||
"dotDotDotToken",
|
||||
"elements",
|
||||
"elementType",
|
||||
"elementTypes",
|
||||
"elseStatement",
|
||||
"escapedText",
|
||||
"exclamationToken",
|
||||
"exportClause",
|
||||
"expression",
|
||||
"exprName",
|
||||
"extendsType",
|
||||
"falseType",
|
||||
"finallyBlock",
|
||||
"flags",
|
||||
"head",
|
||||
"heritageClauses",
|
||||
"importClause",
|
||||
"incrementor",
|
||||
"indexType",
|
||||
"init",
|
||||
"initializer",
|
||||
"isExportEquals",
|
||||
"isTypeOf",
|
||||
"isTypeOnly",
|
||||
"keywordToken",
|
||||
"kind",
|
||||
"label",
|
||||
"left",
|
||||
"literal",
|
||||
"members",
|
||||
"messageText",
|
||||
"modifiers",
|
||||
"moduleReference",
|
||||
"moduleSpecifier",
|
||||
"name",
|
||||
"namedBindings",
|
||||
"objectType",
|
||||
"openingElement",
|
||||
"openingFragment",
|
||||
"operand",
|
||||
"operator",
|
||||
"operatorToken",
|
||||
"parameterName",
|
||||
"parameters",
|
||||
"parseDiagnostics",
|
||||
"properties",
|
||||
"propertyName",
|
||||
"qualifier",
|
||||
"questionDotToken",
|
||||
"questionToken",
|
||||
"right",
|
||||
"selfClosing",
|
||||
"statement",
|
||||
"statements",
|
||||
"tag",
|
||||
"tagName",
|
||||
"template",
|
||||
"templateSpans",
|
||||
"text",
|
||||
"thenStatement",
|
||||
"token",
|
||||
"tokenPos",
|
||||
"trueType",
|
||||
"tryBlock",
|
||||
"type",
|
||||
"typeArguments",
|
||||
"typeName",
|
||||
"typeParameter",
|
||||
"typeParameters",
|
||||
"types",
|
||||
"variableDeclaration",
|
||||
"whenFalse",
|
||||
"whenTrue",
|
||||
];
|
||||
|
||||
/** Property names used in a parse command response, in addition to the AST itself. */
|
||||
const astMetaProperties: string[] = [
|
||||
"ast",
|
||||
"type",
|
||||
];
|
||||
|
||||
/** Property names to extract in an AST response. */
|
||||
const astPropertySet = new Set([...astProperties, ...astMetaProperties]);
|
||||
|
||||
/**
|
||||
* Converts (part of) an AST to a JSON string, ignoring parent pointers.
|
||||
* Converts (part of) an AST to a JSON string, ignoring properties we're not interested in.
|
||||
*/
|
||||
function stringifyAST(obj: any) {
|
||||
return JSON.stringify(obj, (k, v) => {
|
||||
if (isBlacklistedProperty(k)) {
|
||||
return undefined;
|
||||
}
|
||||
return v;
|
||||
// Filter out properties that aren't numeric, empty, or whitelisted.
|
||||
// Note `k` is the empty string for the root object, which is also covered by +k === +k.
|
||||
return (+k === +k || astPropertySet.has(k)) ? v : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -155,8 +262,6 @@ function extractFile(filename: string): string {
|
||||
return stringifyAST({
|
||||
type: "ast",
|
||||
ast,
|
||||
nodeFlags: ts.NodeFlags,
|
||||
syntaxKinds: ts.SyntaxKind
|
||||
});
|
||||
}
|
||||
|
||||
@@ -317,8 +422,10 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
|
||||
let program = project.program;
|
||||
let typeChecker = program.getTypeChecker();
|
||||
|
||||
let diagnostics = program.getSemanticDiagnostics()
|
||||
.filter(d => d.category === ts.DiagnosticCategory.Error);
|
||||
let shouldReportDiagnostics = getEnvironmentVariable("SEMMLE_TYPESCRIPT_REPORT_DIAGNOSTICS", Boolean, false);
|
||||
let diagnostics = shouldReportDiagnostics
|
||||
? program.getSemanticDiagnostics().filter(d => d.category === ts.DiagnosticCategory.Error)
|
||||
: [];
|
||||
if (diagnostics.length > 0) {
|
||||
console.warn('TypeScript: reported ' + diagnostics.length + ' semantic errors.');
|
||||
}
|
||||
@@ -522,6 +629,14 @@ function handlePrepareFilesCommand(command: PrepareFilesCommand) {
|
||||
});
|
||||
}
|
||||
|
||||
function handleGetMetadataCommand(command: GetMetadataCommand) {
|
||||
console.log(JSON.stringify({
|
||||
type: "metadata",
|
||||
syntaxKinds: ts.SyntaxKind,
|
||||
nodeFlags: ts.NodeFlags,
|
||||
}));
|
||||
}
|
||||
|
||||
function reset() {
|
||||
state = new State();
|
||||
state.typeTable.restrictedExpansion = getEnvironmentVariable("SEMMLE_TYPESCRIPT_NO_EXPANSION", Boolean, true);
|
||||
@@ -582,6 +697,9 @@ function runReadLineInterface() {
|
||||
case "reset":
|
||||
handleResetCommand(req);
|
||||
break;
|
||||
case "get-metadata":
|
||||
handleGetMetadataCommand(req);
|
||||
break;
|
||||
case "quit":
|
||||
rl.close();
|
||||
break;
|
||||
|
||||
@@ -2,9 +2,7 @@ package com.semmle.js.parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -163,10 +161,7 @@ import com.semmle.util.data.IntList;
|
||||
*/
|
||||
public class TypeScriptASTConverter {
|
||||
private String source;
|
||||
private final JsonObject nodeFlags;
|
||||
private final JsonObject syntaxKinds;
|
||||
private final Map<Integer, String> nodeFlagMap = new LinkedHashMap<>();
|
||||
private final Map<Integer, String> syntaxKindMap = new LinkedHashMap<>();
|
||||
private final TypeScriptParserMetadata metadata;
|
||||
private int[] lineStarts;
|
||||
|
||||
private int syntaxKindExtends;
|
||||
@@ -180,22 +175,9 @@ public class TypeScriptASTConverter {
|
||||
private static final Pattern WHITESPACE_END_PAREN =
|
||||
Pattern.compile("^" + WHITESPACE_CHAR + "*\\)");
|
||||
|
||||
TypeScriptASTConverter(JsonObject nodeFlags, JsonObject syntaxKinds) {
|
||||
this.nodeFlags = nodeFlags;
|
||||
this.syntaxKinds = syntaxKinds;
|
||||
makeEnumIdMap(nodeFlags, nodeFlagMap);
|
||||
makeEnumIdMap(syntaxKinds, syntaxKindMap);
|
||||
this.syntaxKindExtends = getSyntaxKind("ExtendsKeyword");
|
||||
}
|
||||
|
||||
/** Builds a mapping from ID to name given a TypeScript enum object. */
|
||||
private void makeEnumIdMap(JsonObject enumObject, Map<Integer, String> idToName) {
|
||||
for (Map.Entry<String, JsonElement> entry : enumObject.entrySet()) {
|
||||
JsonPrimitive prim = entry.getValue().getAsJsonPrimitive();
|
||||
if (prim.isNumber() && !idToName.containsKey(prim.getAsInt())) {
|
||||
idToName.put(prim.getAsInt(), entry.getKey());
|
||||
}
|
||||
}
|
||||
TypeScriptASTConverter(TypeScriptParserMetadata metadata) {
|
||||
this.metadata = metadata;
|
||||
this.syntaxKindExtends = metadata.getSyntaxKindId("ExtendsKeyword");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1617,7 +1599,7 @@ public class TypeScriptASTConverter {
|
||||
private Node convertMetaProperty(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
Position metaStart = loc.getStart();
|
||||
String keywordKind =
|
||||
syntaxKinds.get(node.getAsJsonPrimitive("keywordToken").getAsInt() + "").getAsString();
|
||||
metadata.getSyntaxKindName(node.getAsJsonPrimitive("keywordToken").getAsInt());
|
||||
String identifier = keywordKind.equals("ImportKeyword") ? "import" : "new";
|
||||
Position metaEnd =
|
||||
new Position(
|
||||
@@ -1995,7 +1977,7 @@ public class TypeScriptASTConverter {
|
||||
|
||||
private String getOperator(JsonObject node) throws ParseError {
|
||||
int operatorId = node.get("operator").getAsInt();
|
||||
switch (syntaxKindMap.get(operatorId)) {
|
||||
switch (metadata.getSyntaxKindName(operatorId)) {
|
||||
case "PlusPlusToken":
|
||||
return "++";
|
||||
case "MinusMinusToken":
|
||||
@@ -2219,7 +2201,7 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
|
||||
private Node convertTypeOperator(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
String operator = syntaxKinds.get("" + node.get("operator").getAsInt()).getAsString();
|
||||
String operator = metadata.getSyntaxKindName(node.get("operator").getAsInt());
|
||||
if (operator.equals("KeyOfKeyword")) {
|
||||
return new UnaryTypeExpr(loc, UnaryTypeExpr.Kind.Keyof, convertChildAsType(node, "type"));
|
||||
}
|
||||
@@ -2537,12 +2519,7 @@ public class TypeScriptASTConverter {
|
||||
* <tt>ts.NodeFlags</tt> in enum.
|
||||
*/
|
||||
private boolean hasFlag(JsonObject node, String flagName) {
|
||||
JsonElement flagDescriptor = this.nodeFlags.get(flagName);
|
||||
if (flagDescriptor == null) {
|
||||
throw new RuntimeException(
|
||||
"Incompatible version of TypeScript installed. Missing node flag " + flagName);
|
||||
}
|
||||
int flagId = flagDescriptor.getAsInt();
|
||||
int flagId = metadata.getNodeFlagId(flagName);
|
||||
JsonElement flags = node.get("flags");
|
||||
if (flags instanceof JsonPrimitive) {
|
||||
return (flags.getAsInt() & flagId) != 0;
|
||||
@@ -2550,16 +2527,6 @@ public class TypeScriptASTConverter {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Gets the numeric value of the syntax kind enum with the given name. */
|
||||
private int getSyntaxKind(String syntaxKind) {
|
||||
JsonElement descriptor = this.syntaxKinds.get(syntaxKind);
|
||||
if (descriptor == null) {
|
||||
throw new RuntimeException(
|
||||
"Incompatible version of TypeScript installed. Missing syntax kind " + syntaxKind);
|
||||
}
|
||||
return descriptor.getAsInt();
|
||||
}
|
||||
|
||||
/** Check whether a node has a child with a given name. */
|
||||
private boolean hasChild(JsonObject node, String prop) {
|
||||
if (!node.has(prop)) return false;
|
||||
@@ -2581,7 +2548,7 @@ public class TypeScriptASTConverter {
|
||||
if (node instanceof JsonObject) {
|
||||
JsonElement kind = ((JsonObject) node).get("kind");
|
||||
if (kind instanceof JsonPrimitive && ((JsonPrimitive) kind).isNumber())
|
||||
return syntaxKindMap.get(kind.getAsInt());
|
||||
return metadata.getSyntaxKindName(kind.getAsInt());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -150,6 +150,9 @@ public class TypeScriptParser {
|
||||
/** If non-zero, we use this instead of relying on the corresponding environment variable. */
|
||||
private int typescriptRam = 0;
|
||||
|
||||
/** Metadata requested immediately after starting the TypeScript parser. */
|
||||
private TypeScriptParserMetadata metadata;
|
||||
|
||||
/** Sets the amount of RAM to allocate to the TypeScript compiler.s */
|
||||
public void setTypescriptRam(int megabytes) {
|
||||
this.typescriptRam = megabytes;
|
||||
@@ -297,6 +300,7 @@ public class TypeScriptParser {
|
||||
InputStream is = parserWrapperProcess.getInputStream();
|
||||
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
|
||||
fromParserWrapper = new BufferedReader(isr);
|
||||
this.loadMetadata();
|
||||
} catch (IOException e) {
|
||||
throw new CatastrophicError(
|
||||
"Could not start TypeScript parser wrapper " + "(command: ." + parserWrapperCommand + ")",
|
||||
@@ -385,6 +389,17 @@ public class TypeScriptParser {
|
||||
return new CatastrophicError("Unexpected response from TypeScript parser wrapper:\n" + response, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests metadata from the TypeScript process. See {@link TypeScriptParserMetadata}.
|
||||
*/
|
||||
private void loadMetadata() {
|
||||
JsonObject request = new JsonObject();
|
||||
request.add("command", new JsonPrimitive("get-metadata"));
|
||||
JsonObject response = talkToParserWrapper(request);
|
||||
checkResponseType(response, "metadata");
|
||||
this.metadata = new TypeScriptParserMetadata(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AST for a given source file.
|
||||
*
|
||||
@@ -402,11 +417,9 @@ public class TypeScriptParser {
|
||||
metrics.stopPhase(ExtractionMetrics.ExtractionPhase.TypeScriptParser_talkToParserWrapper);
|
||||
try {
|
||||
checkResponseType(response, "ast");
|
||||
JsonObject nodeFlags = response.get("nodeFlags").getAsJsonObject();
|
||||
JsonObject syntaxKinds = response.get("syntaxKinds").getAsJsonObject();
|
||||
JsonObject ast = response.get("ast").getAsJsonObject();
|
||||
metrics.startPhase(ExtractionMetrics.ExtractionPhase.TypeScriptASTConverter_convertAST);
|
||||
Result converted = new TypeScriptASTConverter(nodeFlags, syntaxKinds).convertAST(ast, source);
|
||||
Result converted = new TypeScriptASTConverter(metadata).convertAST(ast, source);
|
||||
metrics.stopPhase(ExtractionMetrics.ExtractionPhase.TypeScriptASTConverter_convertAST);
|
||||
return converted;
|
||||
} catch (IllegalStateException e) {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.semmle.js.parser;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
/**
|
||||
* Static data from the TypeScript compiler needed for decoding ASTs.
|
||||
* <p>
|
||||
* AST nodes store their kind and flags as integers, but the meaning of this integer changes
|
||||
* between compiler versions. The metadata contains mappings from integers to logical names
|
||||
* which are stable across versions.
|
||||
*/
|
||||
public class TypeScriptParserMetadata {
|
||||
private final JsonObject nodeFlags;
|
||||
private final JsonObject syntaxKinds;
|
||||
private final Map<Integer, String> syntaxKindMap = new LinkedHashMap<>();
|
||||
|
||||
public TypeScriptParserMetadata(JsonObject metadata) {
|
||||
this.nodeFlags = metadata.get("nodeFlags").getAsJsonObject();
|
||||
this.syntaxKinds = metadata.get("syntaxKinds").getAsJsonObject();
|
||||
makeEnumIdMap(syntaxKinds, syntaxKindMap);
|
||||
}
|
||||
|
||||
/** Builds a mapping from ID to name given a TypeScript enum object. */
|
||||
private void makeEnumIdMap(JsonObject enumObject, Map<Integer, String> idToName) {
|
||||
for (Map.Entry<String, JsonElement> entry : enumObject.entrySet()) {
|
||||
JsonPrimitive prim = entry.getValue().getAsJsonPrimitive();
|
||||
if (prim.isNumber() && !idToName.containsKey(prim.getAsInt())) {
|
||||
idToName.put(prim.getAsInt(), entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the logical name associated with syntax kind ID <code>id</code>,
|
||||
* or throws an exception if it does not exist.
|
||||
*/
|
||||
String getSyntaxKindName(int id) {
|
||||
String name = syntaxKindMap.get(id);
|
||||
if (name == null) {
|
||||
throw new RuntimeException(
|
||||
"Incompatible version of TypeScript installed. Missing syntax kind ID " + id);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the syntax kind ID corresponding to the logical name <code>name</code>,
|
||||
* or throws an exception if it does not exist.
|
||||
*/
|
||||
int getSyntaxKindId(String name) {
|
||||
JsonElement elm = syntaxKinds.get(name);
|
||||
if (elm == null) {
|
||||
throw new RuntimeException(
|
||||
"Incompatible version of TypeScript installed. Missing syntax kind " + name);
|
||||
}
|
||||
return elm.getAsInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the NodeFlag ID from the logical name <code>name</code>
|
||||
* or throws an exception if it does not exist.
|
||||
*/
|
||||
int getNodeFlagId(String name) {
|
||||
JsonElement elm = nodeFlags.get(name);
|
||||
if (elm == null) {
|
||||
throw new RuntimeException(
|
||||
"Incompatible version of TypeScript installed. Missing node flag " + name);
|
||||
}
|
||||
return elm.getAsInt();
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,10 @@ class RedundantIdemnecantOperand extends RedundantOperand {
|
||||
* arguments to integers. For example, `x&x` is a common idiom for converting `x` to an integer.
|
||||
*/
|
||||
class RedundantIdempotentOperand extends RedundantOperand {
|
||||
RedundantIdempotentOperand() { getParent() instanceof LogicalBinaryExpr }
|
||||
RedundantIdempotentOperand() {
|
||||
getParent() instanceof LogicalBinaryExpr and
|
||||
not exists(UpdateExpr e | e.getParentExpr+() = this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,6 +72,9 @@ predicate benignContext(Expr e) {
|
||||
or
|
||||
// arguments to Promise.resolve (and promise library variants) are benign.
|
||||
e = any(PromiseCreationCall promise).getValue().asExpr()
|
||||
or
|
||||
// arguments to other (unknown) promise creations.
|
||||
e = any(DataFlow::CallNode call | call.getCalleeName() = "resolve").getAnArgument().asExpr()
|
||||
}
|
||||
|
||||
predicate oneshotClosure(DataFlow::CallNode call) {
|
||||
@@ -153,56 +156,6 @@ predicate hasNonVoidReturnType(Function f) {
|
||||
exists(TypeAnnotation type | type = f.getReturnTypeAnnotation() | not type.isVoid())
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes for working with various Deferred implementations.
|
||||
* It is a heuristic. The heuristic assume that a class is a promise defintion
|
||||
* if the class is called "Deferred" and the method `resolve` is called on an instance.
|
||||
*
|
||||
* Removes some false positives in the js/use-of-returnless-function query.
|
||||
*/
|
||||
module Deferred {
|
||||
/**
|
||||
* An instance of a `Deferred` class.
|
||||
* For example the result from `new Deferred()` or `new $.Deferred()`.
|
||||
*/
|
||||
class DeferredInstance extends DataFlow::NewNode {
|
||||
// Describes both `new Deferred()`, `new $.Deferred` and other variants.
|
||||
DeferredInstance() { this.getCalleeName() = "Deferred" }
|
||||
|
||||
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = this
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
|
||||
}
|
||||
|
||||
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A promise object created by a Deferred constructor
|
||||
*/
|
||||
private class DeferredPromiseDefinition extends PromiseDefinition, DeferredInstance {
|
||||
DeferredPromiseDefinition() {
|
||||
// hardening of the "Deferred" heuristic: a method call to `resolve`.
|
||||
exists(ref().getAMethodCall("resolve"))
|
||||
}
|
||||
|
||||
override DataFlow::FunctionNode getExecutor() { result = getCallback(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A resolved promise created by a `new Deferred().resolve()` call.
|
||||
*/
|
||||
class ResolvedDeferredPromiseDefinition extends PromiseCreationCall {
|
||||
ResolvedDeferredPromiseDefinition() {
|
||||
this = any(DeferredPromiseDefinition def).ref().getAMethodCall("resolve")
|
||||
}
|
||||
|
||||
override DataFlow::Node getValue() { result = getArgument(0) }
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::CallNode call, Function func, string name, string msg
|
||||
where
|
||||
(
|
||||
|
||||
@@ -93,6 +93,12 @@ module CharacterEscapes {
|
||||
// conservative formulation: we do not know in general if the sequence is enclosed in a character class `[...]`
|
||||
result = Sets::regexpMetaChars().charAt(_) and
|
||||
mistake = "may still represent a meta-character"
|
||||
) and
|
||||
// avoid the benign case where preceding escaped backslashes turns into backslashes when the regexp is constructed
|
||||
not exists(string raw |
|
||||
not rawStringNode instanceof RegExpLiteral and
|
||||
hasRawStringAndQuote(_, _, rawStringNode, raw) and
|
||||
result = raw.regexpFind("(?<=(^|[^\\\\])((\\\\{3})|(\\\\{7}))).", _, i)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import dataflow.internal.StepSummary
|
||||
|
||||
/**
|
||||
* A definition of a `Promise` object.
|
||||
@@ -121,36 +122,156 @@ class AggregateES2015PromiseDefinition extends PromiseCreationCall {
|
||||
}
|
||||
|
||||
/**
|
||||
* This module defines how data-flow propagates into and out of a Promise.
|
||||
* The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does).
|
||||
* Common predicates shared between type-tracking and data-flow for promises.
|
||||
*/
|
||||
private module PromiseFlow {
|
||||
module Promises {
|
||||
/**
|
||||
* Gets the pseudo-field used to describe resolved values in a promise.
|
||||
*/
|
||||
string resolveField() { result = "$PromiseResolveField$" }
|
||||
string valueProp() { result = "$PromiseResolveField$" }
|
||||
|
||||
/**
|
||||
* Gets the pseudo-field used to describe rejected values in a promise.
|
||||
*/
|
||||
string rejectField() { result = "$PromiseRejectField$" }
|
||||
string errorProp() { result = "$PromiseRejectField$" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A module for supporting promises in type-tracking predicates.
|
||||
* The `PromiseTypeTracking::promiseStep` predicate is used for type tracking in and out of promises,
|
||||
* and is included in the standard type-tracking steps (`SourceNode::track`).
|
||||
* The `TypeTracker::startInPromise()` predicate can be used to initiate a type-tracker
|
||||
* where the tracked value is a promise.
|
||||
*
|
||||
* The below is an example of a type-tracking predicate where the initial value is a promise:
|
||||
* ```
|
||||
* DataFlow::SourceNode myType(DataFlow::TypeTracker t) {
|
||||
* t.startInPromise() and
|
||||
* result = <the promise value> and
|
||||
* or
|
||||
* exists(DataFlow::TypeTracker t2 | result = myType(t2).track(t2, t))
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The type-tracking predicate above will only end (`t = DataFlow::TypeTracker::end()`) after the tracked value has been
|
||||
* extracted from the promise.
|
||||
*
|
||||
* The `PromiseTypeTracking::promiseStep` predicate can be used instead of `SourceNode::track`
|
||||
* to get type-tracking only for promise steps.
|
||||
*
|
||||
* Replace `t.startInPromise()` in the above example with `t.start()` to create a type-tracking predicate
|
||||
* where the value is not initially inside a promise.
|
||||
*/
|
||||
module PromiseTypeTracking {
|
||||
/**
|
||||
* Gets the result from a single step through a promise, from `pred` to `result` summarized by `summary`.
|
||||
* This can be loading a resolved value from a promise, storing a value in a promise, or copying a resolved value from one promise to another.
|
||||
*/
|
||||
DataFlow::SourceNode promiseStep(DataFlow::SourceNode pred, StepSummary summary) {
|
||||
exists(PromiseFlowStep step, string field | field = Promises::valueProp() |
|
||||
summary = LoadStep(field) and
|
||||
step.load(pred, result, field)
|
||||
or
|
||||
summary = StoreStep(field) and
|
||||
step.store(pred, result, field)
|
||||
or
|
||||
summary = LevelStep() and
|
||||
step.loadStore(pred, result, field)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the result from a single step through a promise, from `pred` with tracker `t2` to `result` with tracker `t`.
|
||||
* This can be loading a resolved value from a promise, storing a value in a promise, or copying a resolved value from one promise to another.
|
||||
*/
|
||||
DataFlow::SourceNode promiseStep(
|
||||
DataFlow::SourceNode pred, DataFlow::TypeTracker t, DataFlow::TypeTracker t2
|
||||
) {
|
||||
exists(StepSummary summary |
|
||||
result = PromiseTypeTracking::promiseStep(pred, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A class enabling the use of the `resolveField` as a pseudo-property in type-tracking predicates.
|
||||
*/
|
||||
private class ResolveFieldAsTypeTrackingProperty extends TypeTrackingPseudoProperty {
|
||||
ResolveFieldAsTypeTrackingProperty() { this = Promises::valueProp() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `AdditionalFlowStep` used to model a data-flow step related to promises.
|
||||
*
|
||||
* The `loadStep`/`storeStep`/`loadStoreStep` methods are overloaded such that the new predicates
|
||||
* `load`/`store`/`loadStore` can be used in the `PromiseTypeTracking` module.
|
||||
* (Thereby avoiding conflicts with a "cousin" `AdditionalFlowStep` implementation.)
|
||||
*
|
||||
* The class is private and is only intended to be used inside the `PromiseTypeTracking` and `PromiseFlow` modules.
|
||||
*/
|
||||
abstract private class PromiseFlowStep extends DataFlow::AdditionalFlowStep {
|
||||
final override predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
final override predicate step(
|
||||
DataFlow::Node p, DataFlow::Node s, DataFlow::FlowLabel pl, DataFlow::FlowLabel sl
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
|
||||
*/
|
||||
predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
final override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
this.load(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
|
||||
*/
|
||||
predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
final override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
this.store(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
|
||||
*/
|
||||
predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
final override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
this.loadStore(pred, succ, prop)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This module defines how data-flow propagates into and out of a Promise.
|
||||
* The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does).
|
||||
*/
|
||||
private module PromiseFlow {
|
||||
private predicate valueProp = Promises::valueProp/0;
|
||||
|
||||
private predicate errorProp = Promises::errorProp/0;
|
||||
|
||||
/**
|
||||
* A flow step describing a promise definition.
|
||||
*
|
||||
* The resolved/rejected value is written to a pseudo-field on the promise.
|
||||
*/
|
||||
class PromiseDefitionStep extends DataFlow::AdditionalFlowStep {
|
||||
class PromiseDefitionStep extends PromiseFlowStep {
|
||||
PromiseDefinition promise;
|
||||
|
||||
PromiseDefitionStep() { this = promise }
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = resolveField() and
|
||||
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = valueProp() and
|
||||
pred = promise.getResolveParameter().getACall().getArgument(0) and
|
||||
succ = this
|
||||
or
|
||||
prop = rejectField() and
|
||||
prop = errorProp() and
|
||||
(
|
||||
pred = promise.getRejectParameter().getACall().getArgument(0) or
|
||||
pred = promise.getExecutor().getExceptionalReturn()
|
||||
@@ -158,9 +279,9 @@ private module PromiseFlow {
|
||||
succ = this
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
// Copy the value of a resolved promise to the value of this promise.
|
||||
prop = resolveField() and
|
||||
prop = valueProp() and
|
||||
pred = promise.getResolveParameter().getACall().getArgument(0) and
|
||||
succ = this
|
||||
}
|
||||
@@ -169,20 +290,20 @@ private module PromiseFlow {
|
||||
/**
|
||||
* A flow step describing the a Promise.resolve (and similar) call.
|
||||
*/
|
||||
class CreationStep extends DataFlow::AdditionalFlowStep {
|
||||
class CreationStep extends PromiseFlowStep {
|
||||
PromiseCreationCall promise;
|
||||
|
||||
CreationStep() { this = promise }
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = resolveField() and
|
||||
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = valueProp() and
|
||||
pred = promise.getValue() and
|
||||
succ = this
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
// Copy the value of a resolved promise to the value of this promise.
|
||||
prop = resolveField() and
|
||||
prop = valueProp() and
|
||||
pred = promise.getValue() and
|
||||
succ = this
|
||||
}
|
||||
@@ -192,7 +313,7 @@ private module PromiseFlow {
|
||||
* A load step loading the pseudo-field describing that the promise is rejected.
|
||||
* The rejected value is thrown as a exception.
|
||||
*/
|
||||
class AwaitStep extends DataFlow::AdditionalFlowStep {
|
||||
class AwaitStep extends PromiseFlowStep {
|
||||
DataFlow::Node operand;
|
||||
AwaitExpr await;
|
||||
|
||||
@@ -201,12 +322,12 @@ private module PromiseFlow {
|
||||
operand.getEnclosingExpr() = await.getOperand()
|
||||
}
|
||||
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = resolveField() and
|
||||
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = valueProp() and
|
||||
succ = this and
|
||||
pred = operand
|
||||
or
|
||||
prop = rejectField() and
|
||||
prop = errorProp() and
|
||||
succ = await.getExceptionTarget() and
|
||||
pred = operand
|
||||
}
|
||||
@@ -215,37 +336,37 @@ private module PromiseFlow {
|
||||
/**
|
||||
* A flow step describing the data-flow related to the `.then` method of a promise.
|
||||
*/
|
||||
class ThenStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
|
||||
class ThenStep extends PromiseFlowStep, DataFlow::MethodCallNode {
|
||||
ThenStep() { this.getMethodName() = "then" }
|
||||
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = resolveField() and
|
||||
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = valueProp() and
|
||||
pred = getReceiver() and
|
||||
succ = getCallback(0).getParameter(0)
|
||||
or
|
||||
prop = rejectField() and
|
||||
prop = errorProp() and
|
||||
pred = getReceiver() and
|
||||
succ = getCallback(1).getParameter(0)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
not exists(this.getArgument(1)) and
|
||||
prop = rejectField() and
|
||||
prop = errorProp() and
|
||||
pred = getReceiver() and
|
||||
succ = this
|
||||
or
|
||||
// read the value of a resolved/rejected promise that is returned
|
||||
(prop = rejectField() or prop = resolveField()) and
|
||||
(prop = errorProp() or prop = valueProp()) and
|
||||
pred = getCallback([0 .. 1]).getAReturn() and
|
||||
succ = this
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = resolveField() and
|
||||
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = valueProp() and
|
||||
pred = getCallback([0 .. 1]).getAReturn() and
|
||||
succ = this
|
||||
or
|
||||
prop = rejectField() and
|
||||
prop = errorProp() and
|
||||
pred = getCallback([0 .. 1]).getExceptionalReturn() and
|
||||
succ = this
|
||||
}
|
||||
@@ -254,32 +375,32 @@ private module PromiseFlow {
|
||||
/**
|
||||
* A flow step describing the data-flow related to the `.catch` method of a promise.
|
||||
*/
|
||||
class CatchStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
|
||||
class CatchStep extends PromiseFlowStep, DataFlow::MethodCallNode {
|
||||
CatchStep() { this.getMethodName() = "catch" }
|
||||
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = rejectField() and
|
||||
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = errorProp() and
|
||||
pred = getReceiver() and
|
||||
succ = getCallback(0).getParameter(0)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = resolveField() and
|
||||
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = valueProp() and
|
||||
pred = getReceiver().getALocalSource() and
|
||||
succ = this
|
||||
or
|
||||
// read the value of a resolved/rejected promise that is returned
|
||||
(prop = rejectField() or prop = resolveField()) and
|
||||
(prop = errorProp() or prop = valueProp()) and
|
||||
pred = getCallback(0).getAReturn() and
|
||||
succ = this
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = rejectField() and
|
||||
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = errorProp() and
|
||||
pred = getCallback(0).getExceptionalReturn() and
|
||||
succ = this
|
||||
or
|
||||
prop = resolveField() and
|
||||
prop = valueProp() and
|
||||
pred = getCallback(0).getAReturn() and
|
||||
succ = this
|
||||
}
|
||||
@@ -288,22 +409,22 @@ private module PromiseFlow {
|
||||
/**
|
||||
* A flow step describing the data-flow related to the `.finally` method of a promise.
|
||||
*/
|
||||
class FinallyStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
|
||||
class FinallyStep extends PromiseFlowStep, DataFlow::MethodCallNode {
|
||||
FinallyStep() { this.getMethodName() = "finally" }
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
(prop = resolveField() or prop = rejectField()) and
|
||||
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
(prop = valueProp() or prop = errorProp()) and
|
||||
pred = getReceiver() and
|
||||
succ = this
|
||||
or
|
||||
// read the value of a rejected promise that is returned
|
||||
prop = rejectField() and
|
||||
prop = errorProp() and
|
||||
pred = getCallback(0).getAReturn() and
|
||||
succ = this
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = rejectField() and
|
||||
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = errorProp() and
|
||||
pred = getCallback(0).getExceptionalReturn() and
|
||||
succ = this
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ abstract class Configuration extends string {
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
exists(BarrierGuardNode guard |
|
||||
isBarrierGuardInternal(guard) and
|
||||
guard.internalBlocks(node, "")
|
||||
barrierGuardBlocksNode(guard, node, "")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ abstract class Configuration extends string {
|
||||
predicate isLabeledBarrier(DataFlow::Node node, FlowLabel lbl) {
|
||||
exists(BarrierGuardNode guard |
|
||||
isBarrierGuardInternal(guard) and
|
||||
guard.internalBlocks(node, lbl)
|
||||
barrierGuardBlocksNode(guard, node, lbl)
|
||||
)
|
||||
or
|
||||
none() // relax type inference to account for overriding
|
||||
@@ -199,6 +199,10 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
predicate isBarrierGuard(BarrierGuardNode guard) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `guard` is a barrier guard for this configuration, added through
|
||||
* `isBarrierGuard` or `AdditionalBarrierGuardNode`.
|
||||
*/
|
||||
private predicate isBarrierGuardInternal(BarrierGuardNode guard) {
|
||||
isBarrierGuard(guard)
|
||||
or
|
||||
@@ -319,44 +323,6 @@ module FlowLabel {
|
||||
* implementations of `blocks` will _both_ apply to any configuration that includes either of them.
|
||||
*/
|
||||
abstract class BarrierGuardNode extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if data flow node `nd` acts as a barrier for data flow, possibly due to aliasing
|
||||
* through an access path.
|
||||
*
|
||||
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
|
||||
*
|
||||
* INTERNAL: this predicate should only be used from within `blocks(boolean, Expr)`.
|
||||
*/
|
||||
predicate internalBlocks(DataFlow::Node nd, string label) {
|
||||
// 1) `nd` is a use of a refinement node that blocks its input variable
|
||||
exists(SsaRefinementNode ref, boolean outcome |
|
||||
nd = DataFlow::ssaDefinitionNode(ref) and
|
||||
outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and
|
||||
ssaRefinementBlocks(outcome, ref, label)
|
||||
)
|
||||
or
|
||||
// 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p`
|
||||
exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome |
|
||||
nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and
|
||||
getEnclosingExpr() = cond.getTest() and
|
||||
outcome = cond.getOutcome() and
|
||||
barrierGuardBlocksAccessPath(this, outcome, p, label) and
|
||||
cond.dominates(bb)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists an input variable of `ref` that blocks the label `label`.
|
||||
*
|
||||
* This predicate is outlined to give the optimizer a hint about the join ordering.
|
||||
*/
|
||||
private predicate ssaRefinementBlocks(boolean outcome, SsaRefinementNode ref, string label) {
|
||||
getEnclosingExpr() = ref.getGuard().getTest() and
|
||||
forex(SsaVariable input | input = ref.getAnInput() |
|
||||
barrierGuardBlocksExpr(this, outcome, input.getAUse(), label)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this node blocks expression `e` provided it evaluates to `outcome`.
|
||||
*
|
||||
@@ -387,6 +353,17 @@ private predicate barrierGuardBlocksExpr(
|
||||
guard.(AdditionalBarrierGuardCall).internalBlocksLabel(outcome, test, label)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `guard` may block the flow of a value reachable through exploratory flow.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate barrierGuardIsRelevant(BarrierGuardNode guard) {
|
||||
exists(Expr e |
|
||||
barrierGuardBlocksExpr(guard, _, e, _) and
|
||||
isRelevantForward(e.flow(), _)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through
|
||||
* an access path.
|
||||
@@ -397,9 +374,50 @@ pragma[noinline]
|
||||
private predicate barrierGuardBlocksAccessPath(
|
||||
BarrierGuardNode guard, boolean outcome, AccessPath ap, string label
|
||||
) {
|
||||
barrierGuardIsRelevant(guard) and
|
||||
barrierGuardBlocksExpr(guard, outcome, ap.getAnInstance(), label)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists an input variable of `ref` that blocks the label `label`.
|
||||
*
|
||||
* This predicate is outlined to give the optimizer a hint about the join ordering.
|
||||
*/
|
||||
private predicate barrierGuardBlocksSsaRefinement(
|
||||
BarrierGuardNode guard, boolean outcome, SsaRefinementNode ref, string label
|
||||
) {
|
||||
barrierGuardIsRelevant(guard) and
|
||||
guard.getEnclosingExpr() = ref.getGuard().getTest() and
|
||||
forex(SsaVariable input | input = ref.getAnInput() |
|
||||
barrierGuardBlocksExpr(guard, outcome, input.getAUse(), label)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data flow node `nd` acts as a barrier for data flow, possibly due to aliasing
|
||||
* through an access path.
|
||||
*
|
||||
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
|
||||
*/
|
||||
private predicate barrierGuardBlocksNode(BarrierGuardNode guard, DataFlow::Node nd, string label) {
|
||||
// 1) `nd` is a use of a refinement node that blocks its input variable
|
||||
exists(SsaRefinementNode ref, boolean outcome |
|
||||
nd = DataFlow::ssaDefinitionNode(ref) and
|
||||
outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and
|
||||
barrierGuardBlocksSsaRefinement(guard, outcome, ref, label)
|
||||
)
|
||||
or
|
||||
// 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p`
|
||||
barrierGuardIsRelevant(guard) and
|
||||
exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome |
|
||||
nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and
|
||||
guard.getEnclosingExpr() = cond.getTest() and
|
||||
outcome = cond.getOutcome() and
|
||||
barrierGuardBlocksAccessPath(guard, outcome, p, label) and
|
||||
cond.dominates(bb)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `guard` should block flow along the edge `pred -> succ`.
|
||||
*
|
||||
@@ -408,6 +426,7 @@ private predicate barrierGuardBlocksAccessPath(
|
||||
private predicate barrierGuardBlocksEdge(
|
||||
BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label
|
||||
) {
|
||||
barrierGuardIsRelevant(guard) and
|
||||
exists(
|
||||
SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome
|
||||
|
|
||||
@@ -569,11 +588,12 @@ private class FlowStepThroughImport extends AdditionalFlowStep, DataFlow::ValueN
|
||||
|
||||
/**
|
||||
* Holds if there is a flow step from `pred` to `succ` described by `summary`
|
||||
* under configuration `cfg`.
|
||||
* under configuration `cfg`, disregarding barriers.
|
||||
*
|
||||
* Summary steps through function calls are not taken into account.
|
||||
*/
|
||||
private predicate basicFlowStep(
|
||||
pragma[inline]
|
||||
private predicate basicFlowStepNoBarrier(
|
||||
DataFlow::Node pred, DataFlow::Node succ, PathSummary summary, DataFlow::Configuration cfg
|
||||
) {
|
||||
isLive() and
|
||||
@@ -582,8 +602,7 @@ private predicate basicFlowStep(
|
||||
// Local flow
|
||||
exists(FlowLabel predlbl, FlowLabel succlbl |
|
||||
localFlowStep(pred, succ, cfg, predlbl, succlbl) and
|
||||
not isLabeledBarrierEdge(cfg, pred, succ, predlbl) and
|
||||
not isBarrierEdge(cfg, pred, succ) and
|
||||
not cfg.isBarrierEdge(pred, succ) and
|
||||
summary = MkPathSummary(false, false, predlbl, succlbl)
|
||||
)
|
||||
or
|
||||
@@ -605,6 +624,20 @@ private predicate basicFlowStep(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a flow step from `pred` to `succ` described by `summary`
|
||||
* under configuration `cfg`.
|
||||
*
|
||||
* Summary steps through function calls are not taken into account.
|
||||
*/
|
||||
private predicate basicFlowStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, PathSummary summary, DataFlow::Configuration cfg
|
||||
) {
|
||||
basicFlowStepNoBarrier(pred, succ, summary, cfg) and
|
||||
not isLabeledBarrierEdge(cfg, pred, succ, summary.getStartLabel()) and
|
||||
not isBarrierEdge(cfg, pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a flow step from `pred` to `succ` under configuration `cfg`,
|
||||
* including both basic flow steps and steps into/out of properties.
|
||||
@@ -615,7 +648,7 @@ private predicate basicFlowStep(
|
||||
private predicate exploratoryFlowStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg
|
||||
) {
|
||||
basicFlowStep(pred, succ, _, cfg) or
|
||||
basicFlowStepNoBarrier(pred, succ, _, cfg) or
|
||||
basicStoreStep(pred, succ, _) or
|
||||
basicLoadStep(pred, succ, _) or
|
||||
isAdditionalStoreStep(pred, succ, _, cfg) or
|
||||
@@ -1073,6 +1106,7 @@ private predicate flowStep(
|
||||
// Flow into higher-order call
|
||||
flowIntoHigherOrderCall(pred, succ, cfg, summary)
|
||||
) and
|
||||
isRelevant(succ, cfg) and
|
||||
not cfg.isBarrier(succ) and
|
||||
not isBarrierEdge(cfg, pred, succ) and
|
||||
not isLabeledBarrierEdge(cfg, pred, succ, summary.getEndLabel()) and
|
||||
@@ -1442,6 +1476,7 @@ private class BarrierGuardFunction extends Function {
|
||||
string label;
|
||||
|
||||
BarrierGuardFunction() {
|
||||
barrierGuardIsRelevant(guard) and
|
||||
exists(Expr e |
|
||||
exists(Expr returnExpr |
|
||||
returnExpr = guard.asExpr()
|
||||
|
||||
@@ -8,147 +8,7 @@
|
||||
|
||||
private import javascript
|
||||
private import internal.FlowSteps
|
||||
|
||||
private class PropertyName extends string {
|
||||
PropertyName() {
|
||||
this = any(DataFlow::PropRef pr).getPropertyName()
|
||||
or
|
||||
AccessPath::isAssignedInUniqueFile(this)
|
||||
or
|
||||
exists(AccessPath::getAnAssignmentTo(_, this))
|
||||
}
|
||||
}
|
||||
|
||||
private class OptionalPropertyName extends string {
|
||||
OptionalPropertyName() { this instanceof PropertyName or this = "" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
private newtype TStepSummary =
|
||||
LevelStep() or
|
||||
CallStep() or
|
||||
ReturnStep() or
|
||||
StoreStep(PropertyName prop) or
|
||||
LoadStep(PropertyName prop)
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
|
||||
*
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
class StepSummary extends TStepSummary {
|
||||
/** Gets a textual representation of this step summary. */
|
||||
string toString() {
|
||||
this instanceof LevelStep and result = "level"
|
||||
or
|
||||
this instanceof CallStep and result = "call"
|
||||
or
|
||||
this instanceof ReturnStep and result = "return"
|
||||
or
|
||||
exists(string prop | this = StoreStep(prop) | result = "store " + prop)
|
||||
or
|
||||
exists(string prop | this = LoadStep(prop) | result = "load " + prop)
|
||||
}
|
||||
}
|
||||
|
||||
module StepSummary {
|
||||
/**
|
||||
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
|
||||
*/
|
||||
cached
|
||||
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
|
||||
exists(DataFlow::Node mid | pred.flowsTo(mid) | smallstep(mid, succ, summary))
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `TypeBackTracker.smallstep()` instead.
|
||||
*/
|
||||
predicate smallstep(DataFlow::Node pred, DataFlow::Node succ, StepSummary summary) {
|
||||
// Flow through properties of objects
|
||||
propertyFlowStep(pred, succ) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
// Flow through global variables
|
||||
globalFlowStep(pred, succ) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
// Flow into function
|
||||
callStep(pred, succ) and
|
||||
summary = CallStep()
|
||||
or
|
||||
// Flow out of function
|
||||
returnStep(pred, succ) and
|
||||
summary = ReturnStep()
|
||||
or
|
||||
// Flow through an instance field between members of the same class
|
||||
DataFlow::localFieldStep(pred, succ) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
exists(string prop |
|
||||
basicStoreStep(pred, succ, prop) and
|
||||
summary = StoreStep(prop)
|
||||
or
|
||||
basicLoadStep(pred, succ, prop) and
|
||||
summary = LoadStep(prop)
|
||||
)
|
||||
or
|
||||
any(AdditionalTypeTrackingStep st).step(pred, succ) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
// Store to global access path
|
||||
exists(string name |
|
||||
pred = AccessPath::getAnAssignmentTo(name) and
|
||||
AccessPath::isAssignedInUniqueFile(name) and
|
||||
succ = DataFlow::globalAccessPathRootPseudoNode() and
|
||||
summary = StoreStep(name)
|
||||
)
|
||||
or
|
||||
// Load from global access path
|
||||
exists(string name |
|
||||
succ = AccessPath::getAReferenceTo(name) and
|
||||
AccessPath::isAssignedInUniqueFile(name) and
|
||||
pred = DataFlow::globalAccessPathRootPseudoNode() and
|
||||
summary = LoadStep(name)
|
||||
)
|
||||
or
|
||||
// Store to non-global access path
|
||||
exists(string name |
|
||||
pred = AccessPath::getAnAssignmentTo(succ, name) and
|
||||
summary = StoreStep(name)
|
||||
)
|
||||
or
|
||||
// Load from non-global access path
|
||||
exists(string name |
|
||||
succ = AccessPath::getAReferenceTo(pred, name) and
|
||||
summary = LoadStep(name) and
|
||||
name != ""
|
||||
)
|
||||
or
|
||||
// Summarize calls with flow directly from a parameter to a return.
|
||||
exists(DataFlow::ParameterNode param, DataFlow::FunctionNode fun |
|
||||
(
|
||||
param.flowsTo(fun.getAReturn()) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
exists(string prop |
|
||||
param.getAPropertyRead(prop).flowsTo(fun.getAReturn()) and
|
||||
summary = LoadStep(prop)
|
||||
)
|
||||
) and
|
||||
if param = fun.getAParameter()
|
||||
then
|
||||
// Step from argument to call site.
|
||||
argumentPassing(succ, pred, fun.getFunction(), param)
|
||||
else (
|
||||
// Step from captured parameter to local call sites
|
||||
pred = param and
|
||||
succ = fun.getAnInvocation()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
private import internal.StepSummary
|
||||
|
||||
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalPropertyName prop)
|
||||
|
||||
@@ -216,6 +76,18 @@ class TypeTracker extends TTypeTracker {
|
||||
*/
|
||||
predicate start() { hasCall = false and prop = "" }
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking, and the value starts in the property named `propName`.
|
||||
* The type tracking only ends after the property has been loaded.
|
||||
*/
|
||||
predicate startInProp(PropertyName propName) { hasCall = false and prop = propName }
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking, and the initial value is a promise.
|
||||
* The type tracking only ends after the value has been extracted from the promise.
|
||||
*/
|
||||
predicate startInPromise() { startInProp(Promises::valueProp()) }
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking
|
||||
* when tracking a parameter into a call, but not out of it.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.dataflow.internal.StepSummary
|
||||
|
||||
cached
|
||||
module CallGraph {
|
||||
@@ -83,7 +84,7 @@ module CallGraph {
|
||||
getAFunctionReference(function, 0, t.continue()).flowsTo(callback)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::StepSummary summary, DataFlow::TypeTracker t2 |
|
||||
exists(StepSummary summary, DataFlow::TypeTracker t2 |
|
||||
result = getABoundFunctionReferenceAux(function, boundArgs, t2, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
@@ -91,12 +92,11 @@ module CallGraph {
|
||||
|
||||
pragma[noinline]
|
||||
private DataFlow::SourceNode getABoundFunctionReferenceAux(
|
||||
DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t,
|
||||
DataFlow::StepSummary summary
|
||||
DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t, StepSummary summary
|
||||
) {
|
||||
exists(DataFlow::SourceNode prev |
|
||||
prev = getABoundFunctionReferenceAux(function, boundArgs, t) and
|
||||
DataFlow::StepSummary::step(prev, result, summary)
|
||||
StepSummary::step(prev, result, summary)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,19 @@ private class AnalyzedThisInBoundFunction extends AnalyzedThisExpr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow analysis for `this` expressions in node modules.
|
||||
*
|
||||
* These expressions are assumed to refer to the `module.exports` object.
|
||||
*/
|
||||
private class AnalyzedThisAsModuleExports extends DataFlow::AnalyzedNode, DataFlow::ThisNode {
|
||||
NodeModule m;
|
||||
|
||||
AnalyzedThisAsModuleExports() { m = getBindingContainer() }
|
||||
|
||||
override AbstractValue getALocalValue() { result = TAbstractExportsObject(m) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow analysis for `this` expressions inside a function that is instantiated.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.TypeTracking
|
||||
private import FlowSteps
|
||||
|
||||
class PropertyName extends string {
|
||||
PropertyName() {
|
||||
this = any(DataFlow::PropRef pr).getPropertyName()
|
||||
or
|
||||
AccessPath::isAssignedInUniqueFile(this)
|
||||
or
|
||||
exists(AccessPath::getAnAssignmentTo(_, this))
|
||||
or
|
||||
this instanceof TypeTrackingPseudoProperty
|
||||
}
|
||||
}
|
||||
|
||||
class OptionalPropertyName extends string {
|
||||
OptionalPropertyName() { this instanceof PropertyName or this = "" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A pseudo-property that can be used in type-tracking.
|
||||
*/
|
||||
abstract class TypeTrackingPseudoProperty extends string {
|
||||
bindingset[this]
|
||||
TypeTrackingPseudoProperty() { any() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
newtype TStepSummary =
|
||||
LevelStep() or
|
||||
CallStep() or
|
||||
ReturnStep() or
|
||||
StoreStep(PropertyName prop) or
|
||||
LoadStep(PropertyName prop)
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
|
||||
*
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
class StepSummary extends TStepSummary {
|
||||
/** Gets a textual representation of this step summary. */
|
||||
string toString() {
|
||||
this instanceof LevelStep and result = "level"
|
||||
or
|
||||
this instanceof CallStep and result = "call"
|
||||
or
|
||||
this instanceof ReturnStep and result = "return"
|
||||
or
|
||||
exists(string prop | this = StoreStep(prop) | result = "store " + prop)
|
||||
or
|
||||
exists(string prop | this = LoadStep(prop) | result = "load " + prop)
|
||||
}
|
||||
}
|
||||
|
||||
module StepSummary {
|
||||
/**
|
||||
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
|
||||
*/
|
||||
cached
|
||||
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
|
||||
exists(DataFlow::Node mid | pred.flowsTo(mid) | smallstep(mid, succ, summary))
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `TypeBackTracker.smallstep()` instead.
|
||||
*/
|
||||
predicate smallstep(DataFlow::Node pred, DataFlow::Node succ, StepSummary summary) {
|
||||
// Flow through properties of objects
|
||||
propertyFlowStep(pred, succ) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
// Flow through global variables
|
||||
globalFlowStep(pred, succ) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
// Flow into function
|
||||
callStep(pred, succ) and
|
||||
summary = CallStep()
|
||||
or
|
||||
// Flow out of function
|
||||
returnStep(pred, succ) and
|
||||
summary = ReturnStep()
|
||||
or
|
||||
// Flow through an instance field between members of the same class
|
||||
DataFlow::localFieldStep(pred, succ) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
exists(string prop |
|
||||
basicStoreStep(pred, succ, prop) and
|
||||
summary = StoreStep(prop)
|
||||
or
|
||||
basicLoadStep(pred, succ, prop) and
|
||||
summary = LoadStep(prop)
|
||||
)
|
||||
or
|
||||
any(AdditionalTypeTrackingStep st).step(pred, succ) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
// Store to global access path
|
||||
exists(string name |
|
||||
pred = AccessPath::getAnAssignmentTo(name) and
|
||||
AccessPath::isAssignedInUniqueFile(name) and
|
||||
succ = DataFlow::globalAccessPathRootPseudoNode() and
|
||||
summary = StoreStep(name)
|
||||
)
|
||||
or
|
||||
// Load from global access path
|
||||
exists(string name |
|
||||
succ = AccessPath::getAReferenceTo(name) and
|
||||
AccessPath::isAssignedInUniqueFile(name) and
|
||||
pred = DataFlow::globalAccessPathRootPseudoNode() and
|
||||
summary = LoadStep(name)
|
||||
)
|
||||
or
|
||||
// Store to non-global access path
|
||||
exists(string name |
|
||||
pred = AccessPath::getAnAssignmentTo(succ, name) and
|
||||
summary = StoreStep(name)
|
||||
)
|
||||
or
|
||||
// Load from non-global access path
|
||||
exists(string name |
|
||||
succ = AccessPath::getAReferenceTo(pred, name) and
|
||||
summary = LoadStep(name) and
|
||||
name != ""
|
||||
)
|
||||
or
|
||||
// Step in/out of a promise
|
||||
succ = PromiseTypeTracking::promiseStep(pred, summary)
|
||||
or
|
||||
// Summarize calls with flow directly from a parameter to a return.
|
||||
exists(DataFlow::ParameterNode param, DataFlow::FunctionNode fun |
|
||||
(
|
||||
param.flowsTo(fun.getAReturn()) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
exists(string prop |
|
||||
param.getAPropertyRead(prop).flowsTo(fun.getAReturn()) and
|
||||
summary = LoadStep(prop)
|
||||
)
|
||||
) and
|
||||
if param = fun.getAParameter()
|
||||
then
|
||||
// Step from argument to call site.
|
||||
argumentPassing(succ, pred, fun.getFunction(), param)
|
||||
else (
|
||||
// Step from captured parameter to local call sites
|
||||
pred = param and
|
||||
succ = fun.getAnInvocation()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -697,14 +697,14 @@ abstract private class CallWithAnalyzedParameters extends FunctionWithAnalyzedPa
|
||||
}
|
||||
|
||||
override predicate mayReceiveArgument(Parameter p) {
|
||||
exists(DataFlow::InvokeNode invk, int argIdx |
|
||||
invk = getAnInvocation() and
|
||||
p = getParameter(argIdx)
|
||||
|
|
||||
exists(invk.getArgument(argIdx))
|
||||
or
|
||||
invk.asExpr().(InvokeExpr).isSpreadArgument([0 .. argIdx])
|
||||
exists(int argIdx |
|
||||
p = getParameter(argIdx) and
|
||||
getAnInvocation().getNumArgument() > argIdx
|
||||
)
|
||||
or
|
||||
// All parameters may receive an argument if invoked with a spread argument
|
||||
p = getAParameter() and
|
||||
getAnInvocation().asExpr().(InvokeExpr).isSpreadArgument(_)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -566,36 +566,18 @@ module ClientRequest {
|
||||
* The `isPromise` parameter reflects whether the reference is a promise containing
|
||||
* an instance of `chrome-remote-interface`, or an instance of `chrome-remote-interface`.
|
||||
*/
|
||||
private DataFlow::SourceNode chromeRemoteInterface(DataFlow::TypeTracker t, boolean isPromise) {
|
||||
t.start() and
|
||||
private DataFlow::SourceNode chromeRemoteInterface(DataFlow::TypeTracker t) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = DataFlow::moduleImport("chrome-remote-interface").getAnInvocation()
|
||||
|
|
||||
result = call and isPromise = true
|
||||
// the client is inside in a promise.
|
||||
t.startInPromise() and result = call
|
||||
or
|
||||
result = call.getCallback([0 .. 1]).getParameter(0) and isPromise = false
|
||||
// the client is accessed directly using a callback.
|
||||
t.start() and result = call.getCallback([0 .. 1]).getParameter(0)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = chromeRemoteInterface(t2, isPromise).track(t2, t))
|
||||
or
|
||||
// Simple promise tracking.
|
||||
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred |
|
||||
pred = chromeRemoteInterface(t2, true) and
|
||||
isPromise = false and
|
||||
(
|
||||
t2 = t and
|
||||
exists(AwaitExpr await | DataFlow::valueNode(await.getOperand()).getALocalSource() = pred |
|
||||
result.getEnclosingExpr() = await
|
||||
)
|
||||
or
|
||||
t2 = t and
|
||||
exists(DataFlow::MethodCallNode thenCall |
|
||||
thenCall.getMethodName() = "then" and pred = thenCall.getReceiver().getALocalSource()
|
||||
|
|
||||
result = thenCall.getCallback(0).getParameter(0)
|
||||
)
|
||||
)
|
||||
)
|
||||
exists(DataFlow::TypeTracker t2 | result = chromeRemoteInterface(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -606,7 +588,7 @@ module ClientRequest {
|
||||
|
||||
ChromeRemoteInterfaceRequest() {
|
||||
exists(DataFlow::SourceNode instance |
|
||||
instance = chromeRemoteInterface(DataFlow::TypeTracker::end(), false)
|
||||
instance = chromeRemoteInterface(DataFlow::TypeTracker::end())
|
||||
|
|
||||
optionsArg = 0 and
|
||||
this = instance.getAPropertyRead("Page").getAMemberCall("navigate")
|
||||
|
||||
@@ -4,6 +4,23 @@
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A call that can produce a file name.
|
||||
*/
|
||||
abstract private class FileNameProducer extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a file name produced by this producer.
|
||||
*/
|
||||
abstract DataFlow::Node getAFileName();
|
||||
}
|
||||
|
||||
/**
|
||||
* A node that contains a file name, and is produced by a `ProducesFileNames`.
|
||||
*/
|
||||
private class ProducedFileName extends FileNameSource {
|
||||
ProducedFileName() { this = any(FileNameProducer producer).getAFileName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A file name from the `walk-sync` library.
|
||||
*/
|
||||
@@ -58,33 +75,41 @@ private class GlobFileNameSource extends FileNameSource {
|
||||
}
|
||||
|
||||
/**
|
||||
* A file name or an array of file names from the `globby` library.
|
||||
* Gets a file name or an array of file names from the `globby` library.
|
||||
* The predicate uses type-tracking. However, type-tracking is only used to track a step out of a promise.
|
||||
*/
|
||||
private class GlobbyFileNameSource extends FileNameSource {
|
||||
GlobbyFileNameSource() {
|
||||
private DataFlow::SourceNode globbyFileNameSource(DataFlow::TypeTracker t) {
|
||||
exists(string moduleName | moduleName = "globby" |
|
||||
// `require('globby').sync(_)`
|
||||
this = DataFlow::moduleMember(moduleName, "sync").getACall()
|
||||
t.start() and
|
||||
result = DataFlow::moduleMember(moduleName, "sync").getACall()
|
||||
or
|
||||
// `files` in `require('globby')(_).then(files => ...)`
|
||||
this =
|
||||
DataFlow::moduleImport(moduleName)
|
||||
.getACall()
|
||||
.getAMethodCall("then")
|
||||
.getCallback(0)
|
||||
.getParameter(0)
|
||||
t.startInPromise() and
|
||||
result = DataFlow::moduleImport(moduleName).getACall()
|
||||
)
|
||||
or
|
||||
// Tracking out of a promise
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = PromiseTypeTracking::promiseStep(globbyFileNameSource(t2), t, t2)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A file name or an array of file names from the `fast-glob` library.
|
||||
* A file name or an array of file names from the `globby` library.
|
||||
*/
|
||||
private class FastGlobFileNameSource extends FileNameSource {
|
||||
FastGlobFileNameSource() {
|
||||
private class GlobbyFileNameSource extends FileNameSource {
|
||||
GlobbyFileNameSource() { this = globbyFileNameSource(DataFlow::TypeTracker::end()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file name or an array of file names from the `fast-glob` library.
|
||||
* The predicate uses type-tracking. However, type-tracking is only used to track a step out of a promise.
|
||||
*/
|
||||
private DataFlow::Node fastGlobFileNameSource(DataFlow::TypeTracker t) {
|
||||
exists(string moduleName | moduleName = "fast-glob" |
|
||||
// `require('fast-glob').sync(_)`
|
||||
this = DataFlow::moduleMember(moduleName, "sync").getACall()
|
||||
// `require('fast-glob').sync(_)
|
||||
t.start() and result = DataFlow::moduleMember(moduleName, "sync").getACall()
|
||||
or
|
||||
exists(DataFlow::SourceNode f |
|
||||
f = DataFlow::moduleImport(moduleName)
|
||||
@@ -93,16 +118,168 @@ private class FastGlobFileNameSource extends FileNameSource {
|
||||
|
|
||||
// `files` in `require('fast-glob')(_).then(files => ...)` and
|
||||
// `files` in `require('fast-glob').async(_).then(files => ...)`
|
||||
this = f.getACall().getAMethodCall("then").getCallback(0).getParameter(0)
|
||||
t.startInPromise() and result = f.getACall()
|
||||
)
|
||||
or
|
||||
// `file` in `require('fast-glob').stream(_).on(_, file => ...)`
|
||||
this =
|
||||
t.start() and
|
||||
result =
|
||||
DataFlow::moduleMember(moduleName, "stream")
|
||||
.getACall()
|
||||
.getAMethodCall(EventEmitter::on())
|
||||
.getCallback(1)
|
||||
.getParameter(0)
|
||||
)
|
||||
or
|
||||
// Tracking out of a promise
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = PromiseTypeTracking::promiseStep(fastGlobFileNameSource(t2), t, t2)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A file name or an array of file names from the `fast-glob` library.
|
||||
*/
|
||||
private class FastGlobFileNameSource extends FileNameSource {
|
||||
FastGlobFileNameSource() { this = fastGlobFileNameSource(DataFlow::TypeTracker::end()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes and predicates for modelling the `fstream` library (https://www.npmjs.com/package/fstream).
|
||||
*/
|
||||
private module FStream {
|
||||
/**
|
||||
* Gets a reference to a method in the `fstream` library.
|
||||
*/
|
||||
private DataFlow::SourceNode getAnFStreamProperty() {
|
||||
exists(DataFlow::SourceNode mod, string readOrWrite, string subMod |
|
||||
mod = DataFlow::moduleImport("fstream") and
|
||||
(readOrWrite = "Reader" or readOrWrite = "Writer") and
|
||||
(subMod = "File" or subMod = "Dir" or subMod = "Link" or subMod = "Proxy")
|
||||
|
|
||||
result = mod.getAPropertyRead(readOrWrite) or
|
||||
result = mod.getAPropertyRead(readOrWrite).getAPropertyRead(subMod) or
|
||||
result = mod.getAPropertyRead(subMod).getAPropertyRead(readOrWrite)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation of a method defined in the `fstream` library.
|
||||
*/
|
||||
private class FStream extends FileSystemAccess, DataFlow::InvokeNode {
|
||||
FStream() { this = getAnFStreamProperty().getAnInvocation() }
|
||||
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
result = getOptionArgument(0, "path")
|
||||
or
|
||||
not exists(getOptionArgument(0, "path")) and
|
||||
result = getArgument(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the library `write-file-atomic`.
|
||||
*/
|
||||
private class WriteFileAtomic extends FileSystemWriteAccess, DataFlow::CallNode {
|
||||
WriteFileAtomic() {
|
||||
this = DataFlow::moduleImport("write-file-atomic").getACall()
|
||||
or
|
||||
this = DataFlow::moduleMember("write-file-atomic", "sync").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
|
||||
|
||||
override DataFlow::Node getADataNode() { result = getArgument(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the library `recursive-readdir`.
|
||||
*/
|
||||
private class RecursiveReadDir extends FileSystemAccess, FileNameProducer, DataFlow::CallNode {
|
||||
RecursiveReadDir() { this = DataFlow::moduleImport("recursive-readdir").getACall() }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
|
||||
|
||||
override DataFlow::Node getAFileName() { result = getCallback([1 .. 2]).getParameter(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes and predicates for modelling the `jsonfile` library (https://www.npmjs.com/package/jsonfile).
|
||||
*/
|
||||
private module JSONFile {
|
||||
/**
|
||||
* A reader for JSON files.
|
||||
*/
|
||||
class JSONFileReader extends FileSystemReadAccess, DataFlow::CallNode {
|
||||
JSONFileReader() {
|
||||
this =
|
||||
DataFlow::moduleMember("jsonfile", any(string s | s = "readFile" or s = "readFileSync"))
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
|
||||
|
||||
override DataFlow::Node getADataNode() {
|
||||
this.getCalleeName() = "readFile" and result = getCallback([1 .. 2]).getParameter(1)
|
||||
or
|
||||
this.getCalleeName() = "readFileSync" and result = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A writer for JSON files.
|
||||
*/
|
||||
class JSONFileWriter extends FileSystemWriteAccess, DataFlow::CallNode {
|
||||
JSONFileWriter() {
|
||||
this =
|
||||
DataFlow::moduleMember("jsonfile", any(string s | s = "writeFile" or s = "writeFileSync"))
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
|
||||
|
||||
override DataFlow::Node getADataNode() { result = getArgument(1) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A file system access made by a NodeJS library.
|
||||
* This class models multiple NodeJS libraries that access files.
|
||||
*/
|
||||
private class LibraryAccess extends FileSystemAccess, DataFlow::InvokeNode {
|
||||
int pathArgument; // The index of the path argument.
|
||||
|
||||
LibraryAccess() {
|
||||
pathArgument = 0 and
|
||||
(
|
||||
this = DataFlow::moduleImport("path-exists").getACall()
|
||||
or
|
||||
this = DataFlow::moduleImport("rimraf").getACall()
|
||||
or
|
||||
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"
|
||||
)).getACall()
|
||||
)
|
||||
or
|
||||
pathArgument = 0 and
|
||||
this =
|
||||
DataFlow::moduleMember("vinyl-fs", any(string s | s = "src" or s = "dest" or s = "symlink"))
|
||||
.getACall()
|
||||
or
|
||||
pathArgument = [0 .. 1] and
|
||||
(
|
||||
this = DataFlow::moduleImport("ncp").getACall() or
|
||||
this = DataFlow::moduleMember("ncp", "ncp").getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = getArgument(pathArgument) }
|
||||
}
|
||||
|
||||
@@ -23,8 +23,16 @@ private module MongoDB {
|
||||
*/
|
||||
private DataFlow::SourceNode getAMongoClient(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result = mongodb().getAPropertyRead("MongoClient")
|
||||
or
|
||||
exists(DataFlow::ParameterNode p |
|
||||
p = result and
|
||||
p = getAMongoDbCallback().getParameter(1) and
|
||||
not p.getName().toLowerCase() = "db" // mongodb v2 provides a `Db` here
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = getAMongoClient(t2).track(t2, t))
|
||||
}
|
||||
|
||||
@@ -36,7 +44,7 @@ private module MongoDB {
|
||||
/** Gets a data flow node that leads to a `connect` callback. */
|
||||
private DataFlow::SourceNode getAMongoDbCallback(DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
result = getAMongoClient().getAMemberCall("connect").getArgument(1).getALocalSource()
|
||||
result = getAMongoClient().getAMemberCall("connect").getLastArgument().getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | result = getAMongoDbCallback(t2).backtrack(t2, t))
|
||||
}
|
||||
@@ -51,7 +59,15 @@ private module MongoDB {
|
||||
*/
|
||||
private DataFlow::SourceNode getAMongoDb(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = getAMongoDbCallback().getParameter(1)
|
||||
(
|
||||
exists(DataFlow::ParameterNode p |
|
||||
p = result and
|
||||
p = getAMongoDbCallback().getParameter(1) and
|
||||
not p.getName().toLowerCase() = "client" // mongodb v3 provides a `Mongoclient` here
|
||||
)
|
||||
or
|
||||
result = getAMongoClient().getAMethodCall("db")
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = getAMongoDb(t2).track(t2, t))
|
||||
}
|
||||
@@ -104,43 +120,71 @@ private module MongoDB {
|
||||
|
||||
QueryCall() {
|
||||
exists(string m | this = getACollection().getAMethodCall(m) |
|
||||
m = "aggregate" and queryArgIdx = 0
|
||||
or
|
||||
m = "count" and queryArgIdx = 0
|
||||
or
|
||||
m = "deleteMany" and queryArgIdx = 0
|
||||
or
|
||||
m = "deleteOne" and queryArgIdx = 0
|
||||
or
|
||||
m = "distinct" and queryArgIdx = 1
|
||||
or
|
||||
m = "find" and queryArgIdx = 0
|
||||
or
|
||||
m = "findOne" and queryArgIdx = 0
|
||||
or
|
||||
m = "findOneAndDelete" and queryArgIdx = 0
|
||||
or
|
||||
m = "findOneAndRemove" and queryArgIdx = 0
|
||||
or
|
||||
m = "findOneAndDelete" and queryArgIdx = 0
|
||||
or
|
||||
m = "findOneAndUpdate" and queryArgIdx = 0
|
||||
or
|
||||
m = "remove" and queryArgIdx = 0
|
||||
or
|
||||
m = "replaceOne" and queryArgIdx = 0
|
||||
or
|
||||
m = "update" and queryArgIdx = 0
|
||||
or
|
||||
m = "updateMany" and queryArgIdx = 0
|
||||
or
|
||||
m = "updateOne" and queryArgIdx = 0
|
||||
CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides signatures for the Collection methods.
|
||||
*/
|
||||
module CollectionMethodSignatures {
|
||||
/**
|
||||
* Holds if Collection method `name` interprets parameter `n` as a query.
|
||||
*/
|
||||
predicate interpretsArgumentAsQuery(string name, int n) {
|
||||
// FilterQuery
|
||||
(
|
||||
name = "aggregate" and n = 0
|
||||
or
|
||||
name = "count" and n = 0
|
||||
or
|
||||
name = "countDocuments" and n = 0
|
||||
or
|
||||
name = "deleteMany" and n = 0
|
||||
or
|
||||
name = "deleteOne" and n = 0
|
||||
or
|
||||
name = "distinct" and n = 1
|
||||
or
|
||||
name = "find" and n = 0
|
||||
or
|
||||
name = "findOne" and n = 0
|
||||
or
|
||||
name = "findOneAndDelete" and n = 0
|
||||
or
|
||||
name = "findOneAndRemove" and n = 0
|
||||
or
|
||||
name = "findOneAndReplace" and n = 0
|
||||
or
|
||||
name = "findOneAndUpdate" and n = 0
|
||||
or
|
||||
name = "remove" and n = 0
|
||||
or
|
||||
name = "replaceOne" and n = 0
|
||||
or
|
||||
name = "update" and n = 0
|
||||
or
|
||||
name = "updateMany" and n = 0
|
||||
or
|
||||
name = "updateOne" and n = 0
|
||||
)
|
||||
or
|
||||
// UpdateQuery
|
||||
(
|
||||
name = "findOneAndUpdate" and n = 1
|
||||
or
|
||||
name = "update" and n = 1
|
||||
or
|
||||
name = "updateMany" and n = 1
|
||||
or
|
||||
name = "updateOne" and n = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that is interpreted as a MongoDB query.
|
||||
*/
|
||||
@@ -166,10 +210,241 @@ private module Mongoose {
|
||||
}
|
||||
|
||||
/**
|
||||
* A Mongoose collection object.
|
||||
* Provides classes modeling the Mongoose Model class
|
||||
*/
|
||||
class Model extends MongoDB::Collection {
|
||||
Model() { this = getAMongooseInstance().getAMemberCall("model") }
|
||||
module Model {
|
||||
/**
|
||||
* Gets a data flow node referring to a Mongoose Model object.
|
||||
*/
|
||||
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
||||
(
|
||||
result = getAMongooseInstance().getAMemberCall("model") or
|
||||
result.hasUnderlyingType("mongoose", "Model")
|
||||
) and
|
||||
t.start()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node referring to a Mongoose model object.
|
||||
*/
|
||||
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Provides signatures for the Model methods.
|
||||
*/
|
||||
module MethodSignatures {
|
||||
/**
|
||||
* Holds if Model method `name` interprets parameter `n` as a query.
|
||||
*/
|
||||
predicate interpretsArgumentAsQuery(string name, int n) {
|
||||
// implement lots of the MongoDB collection interface
|
||||
MongoDB::CollectionMethodSignatures::interpretsArgumentAsQuery(name, n)
|
||||
or
|
||||
name = "findByIdAndUpdate" and n = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = "replaceOne" or
|
||||
name = "update" or
|
||||
name = "updateMany" or
|
||||
name = "updateOne" or
|
||||
name = "where"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes modeling the Mongoose Query class
|
||||
*/
|
||||
module Query {
|
||||
/**
|
||||
* A Mongoose query object as a result of a Model method call.
|
||||
*/
|
||||
private class QueryFromModel extends DataFlow::MethodCallNode {
|
||||
QueryFromModel() {
|
||||
exists(string name |
|
||||
Model::MethodSignatures::returnsQuery(name) and
|
||||
Model::ref().getAMethodCall(name) = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Mongoose query object as a result of a Query constructor invocation.
|
||||
*/
|
||||
class QueryFromConstructor extends DataFlow::NewNode {
|
||||
QueryFromConstructor() {
|
||||
this = getAMongooseInstance().getAPropertyRead("Query").getAnInstantiation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node referring to a Mongoose query object.
|
||||
*/
|
||||
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
||||
(
|
||||
result instanceof QueryFromConstructor or
|
||||
result instanceof QueryFromModel or
|
||||
result.hasUnderlyingType("mongoose", "Query")
|
||||
) and
|
||||
t.start()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode succ | succ = ref(t2) |
|
||||
result = succ.track(t2, t)
|
||||
or
|
||||
result = succ.getAMethodCall(any(string name | MethodSignatures::returnsQuery(name))) and
|
||||
t = t2.continue()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node referring to a Mongoose query object.
|
||||
*/
|
||||
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Provides signatures for the Query methods.
|
||||
*/
|
||||
module MethodSignatures {
|
||||
/**
|
||||
* Holds if Query method `name` interprets parameter `n` as a query.
|
||||
*/
|
||||
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"
|
||||
)
|
||||
or
|
||||
n = 1 and
|
||||
(
|
||||
name = "distinct" or
|
||||
name = "findOneAndUpdate" or
|
||||
name = "update" or
|
||||
name = "updateMany" or
|
||||
name = "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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,4 +463,58 @@ private module Mongoose {
|
||||
|
||||
override string getCredentialsKind() { result = kind }
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that is interpreted as a (part of a) MongoDB query.
|
||||
*/
|
||||
class MongoDBQueryPart extends NoSQL::Query {
|
||||
MongoDBQueryPart() {
|
||||
exists(DataFlow::MethodCallNode mcn, string method, int n |
|
||||
Model::MethodSignatures::interpretsArgumentAsQuery(method, n) and
|
||||
mcn = Model::ref().getAMethodCall(method) and
|
||||
this = mcn.getArgument(n).asExpr()
|
||||
)
|
||||
or
|
||||
this = any(Query::QueryFromConstructor c).getArgument(2).asExpr()
|
||||
or
|
||||
exists(string method, int n | Query::MethodSignatures::interpretsArgumentAsQuery(method, n) |
|
||||
this = Query::ref().getAMethodCall(method).getArgument(n).asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An evaluation of a MongoDB query.
|
||||
*/
|
||||
class MongoDBQueryEvaluation extends DatabaseAccess {
|
||||
DataFlow::MethodCallNode mcn;
|
||||
|
||||
MongoDBQueryEvaluation() {
|
||||
this = mcn and
|
||||
(
|
||||
exists(string method |
|
||||
Model::MethodSignatures::returnsQuery(method) and
|
||||
mcn = Model::ref().getAMethodCall(method) and
|
||||
// callback provided to a Model method call
|
||||
exists(mcn.getCallback(mcn.getNumArgument() - 1))
|
||||
)
|
||||
or
|
||||
Query::ref().getAMethodCall() = mcn and
|
||||
(
|
||||
// explicit execution using a Query method call
|
||||
exists(string executor | executor = "exec" or executor = "then" or executor = "catch" |
|
||||
mcn.getMethodName() = executor
|
||||
)
|
||||
or
|
||||
// callback provided to a Query method call
|
||||
exists(mcn.getCallback(mcn.getNumArgument() - 1))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
// NB: this does not account for all of the chained calls leading to this execution
|
||||
mcn.getAnArgument().asExpr().(MongoDBQueryPart).flow() = result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,26 +258,145 @@ private class JQueryChainedElement extends DOM::Element, InvokeExpr {
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of a URL request made using the `jQuery.ajax` or `jQuery.getJSON`.
|
||||
* Classes and predicates for modelling `ClientRequest`s in JQuery.
|
||||
*/
|
||||
private class JQueryClientRequest extends ClientRequest::Range {
|
||||
JQueryClientRequest() {
|
||||
exists(string name |
|
||||
name = "ajax" or
|
||||
name = "getJSON"
|
||||
|
|
||||
this = jquery().getAMemberCall(name)
|
||||
)
|
||||
}
|
||||
private module JQueryClientRequest {
|
||||
/**
|
||||
* A model of a URL request made using the `jQuery.ajax`.
|
||||
*/
|
||||
private class JQueryAjaxCall extends ClientRequest::Range {
|
||||
JQueryAjaxCall() { this = jquery().getAMemberCall("ajax") }
|
||||
|
||||
override DataFlow::Node getUrl() {
|
||||
result = getArgument(0) or
|
||||
result = getArgument(0) and not exists(getOptionArgument(0, _))
|
||||
or
|
||||
result = getOptionArgument([0 .. 1], "url")
|
||||
}
|
||||
|
||||
override DataFlow::Node getHost() { none() }
|
||||
|
||||
override DataFlow::Node getADataNode() { result = getOptionArgument([0 .. 1], "data") }
|
||||
|
||||
private string getResponseType() {
|
||||
getOptionArgument([0 .. 1], "dataType").mayHaveStringValue(result)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
|
||||
(
|
||||
responseType = getResponseType()
|
||||
or
|
||||
not exists(getResponseType()) and responseType = ""
|
||||
) and
|
||||
promise = false and
|
||||
(
|
||||
result =
|
||||
getOptionArgument([0 .. 1], "success")
|
||||
.getALocalSource()
|
||||
.(DataFlow::FunctionNode)
|
||||
.getParameter(0)
|
||||
or
|
||||
result =
|
||||
getAResponseNodeFromAnXHRObject(getOptionArgument([0 .. 1],
|
||||
any(string method | method = "error" or method = "complete"))
|
||||
.getALocalSource()
|
||||
.(DataFlow::FunctionNode)
|
||||
.getParameter(0))
|
||||
or
|
||||
result = getAnAjaxCallbackDataNode(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a response data node from a call to a method on jqXHR Object `request`.
|
||||
*/
|
||||
private DataFlow::Node getAnAjaxCallbackDataNode(ClientRequest::Range request) {
|
||||
result =
|
||||
request
|
||||
.getAMemberCall(any(string s | s = "done" or s = "then"))
|
||||
.getCallback(0)
|
||||
.getParameter(0)
|
||||
or
|
||||
result =
|
||||
getAResponseNodeFromAnXHRObject(request.getAMemberCall("fail").getCallback(0).getParameter(0))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node refering to the response contained in an `jqXHR` object.
|
||||
*/
|
||||
private DataFlow::SourceNode getAResponseNodeFromAnXHRObject(DataFlow::SourceNode obj) {
|
||||
result =
|
||||
obj
|
||||
.getAPropertyRead(any(string s |
|
||||
s = "responseText" or
|
||||
s = "responseXML"
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of a URL request made using a `jQuery.ajax` shorthand.
|
||||
* E.g. `jQuery.getJSON`, `jQuery.post` etc.
|
||||
* See: https://api.jquery.com/category/ajax/shorthand-methods/.
|
||||
*
|
||||
* Models the following method signatures:
|
||||
* - `jQuery.get( url [, data ] [, success ] [, dataType ] )`
|
||||
* - `jQuery.getJSON( url [, data ] [, success ] )`
|
||||
* - `jQuery.getScript( url [, success ] )`
|
||||
* - `jQuery.post( url [, data ] [, success ] [, dataType ] )`
|
||||
* - `.load( url [, data ] [, complete ] )`
|
||||
*/
|
||||
private class JQueryAjaxShortHand extends ClientRequest::Range {
|
||||
string name;
|
||||
|
||||
JQueryAjaxShortHand() {
|
||||
(
|
||||
name = "get" or
|
||||
name = "getJSON" or
|
||||
name = "getScript" or
|
||||
name = "post"
|
||||
) and
|
||||
this = jquery().getAMemberCall(name)
|
||||
or
|
||||
name = "load" and
|
||||
this = JQuery::objectRef().getAMethodCall(name)
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrl() { result = getArgument(0) }
|
||||
|
||||
override DataFlow::Node getHost() { none() }
|
||||
|
||||
override DataFlow::Node getADataNode() {
|
||||
result = getArgument(1) and
|
||||
not name = "getScript" and // doesn't have a data-node.
|
||||
not result.getALocalSource() instanceof DataFlow::FunctionNode // looks like the success callback.
|
||||
}
|
||||
|
||||
private string getResponseType() {
|
||||
(name = "get" or name = "post") and
|
||||
getLastArgument().mayHaveStringValue(result) and
|
||||
getNumArgument() > 1
|
||||
or
|
||||
name = "getJSON" and result = "json"
|
||||
or
|
||||
(name = "getScript" or name = "load") and
|
||||
result = "text"
|
||||
}
|
||||
|
||||
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
|
||||
(
|
||||
responseType = getResponseType()
|
||||
or
|
||||
not exists(getResponseType()) and responseType = ""
|
||||
) and
|
||||
promise = false and
|
||||
(
|
||||
// one of the two last arguments
|
||||
result = getCallback([getNumArgument() - 2 .. getNumArgument() - 1]).getParameter(0)
|
||||
or
|
||||
result = getAnAjaxCallbackDataNode(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module JQuery {
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
| module-exports.js:3:1:3:4 | this | file://:0:0:0:0 | indefinite value (call) |
|
||||
| module-exports.js:3:1:3:4 | this | module-exports.js:1:1:8:0 | exports object of module module-exports |
|
||||
| module-exports.js:4:28:4:31 | this | file://:0:0:0:0 | indefinite value (call) |
|
||||
| module-exports.js:4:28:4:31 | this | module-exports.js:1:1:8:0 | exports object of module module-exports |
|
||||
| module-exports.js:4:28:4:31 | this | module-exports.js:4:15:4:34 | instance of anonymous function |
|
||||
| module-exports.js:5:35:5:38 | this | file://:0:0:0:0 | indefinite value (call) |
|
||||
| module-exports.js:5:35:5:38 | this | file://:0:0:0:0 | indefinite value (heap) |
|
||||
| module-exports.js:5:35:5:38 | this | module-exports.js:1:1:8:0 | exports object of module module-exports |
|
||||
| module-exports.js:5:35:5:38 | this | module-exports.js:5:22:5:41 | instance of anonymous function |
|
||||
| module-exports.js:6:21:6:24 | this | file://:0:0:0:0 | indefinite value (call) |
|
||||
| module-exports.js:6:21:6:24 | this | module-exports.js:1:1:8:0 | exports object of module module-exports |
|
||||
| module-exports.js:7:28:7:31 | this | file://:0:0:0:0 | indefinite value (call) |
|
||||
| module-exports.js:7:28:7:31 | this | module-exports.js:1:1:8:0 | exports object of module module-exports |
|
||||
| tst.js:4:9:4:12 | this | file://:0:0:0:0 | indefinite value (call) |
|
||||
| tst.js:4:9:4:12 | this | tst.js:1:1:11:1 | instance of class C_normal |
|
||||
| tst.js:8:15:8:18 | this | file://:0:0:0:0 | indefinite value (call) |
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
missingBasicBlock
|
||||
basicBlock
|
||||
| module-exports.js:1:1:1:0 | this | module-exports.js:1:1:1:0 | entry node of <toplevel> |
|
||||
| module-exports.js:4:15:4:14 | this | module-exports.js:4:15:4:14 | entry node of function() { this; } |
|
||||
| module-exports.js:5:22:5:21 | this | module-exports.js:5:22:5:21 | entry node of function() { this; } |
|
||||
| tst.js:1:1:1:0 | this | tst.js:1:1:1:0 | entry node of <toplevel> |
|
||||
| tst.js:1:16:1:15 | this | tst.js:1:16:1:15 | entry node of () {} |
|
||||
| tst.js:3:7:3:6 | this | tst.js:3:7:3:6 | entry node of () {\\n ... ;\\n } |
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
| module-exports.js:4:28:4:31 | this | module-exports.js:4:15:4:34 | function() { this; } |
|
||||
| module-exports.js:5:35:5:38 | this | module-exports.js:5:22:5:41 | function() { this; } |
|
||||
| tst.js:4:9:4:12 | this | tst.js:3:7:5:5 | () {\\n ... ;\\n } |
|
||||
| tst.js:8:15:8:18 | this | tst.js:7:7:9:5 | () {\\n ... ;\\n } |
|
||||
| tst.js:17:13:17:16 | this | tst.js:16:10:18:9 | functio ... } |
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
var fs = require('fs');
|
||||
|
||||
this;
|
||||
exports.foo = function() { this; };
|
||||
module.exports.bar = function() { this; };
|
||||
exports.baz = () => this;
|
||||
module.exports.qux = () => this;
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: --tolerate-parse-errors
|
||||
@@ -0,0 +1,2 @@
|
||||
| tst.ts:1:1:1:1 | Error: '}' expected. |
|
||||
| tst.ts:1:25:1:25 | Error: '{' expected. |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from JSParseError err
|
||||
select err
|
||||
@@ -0,0 +1 @@
|
||||
var fn = (x: number) => return x * 2;
|
||||
@@ -55,6 +55,17 @@ test_ClientRequest
|
||||
| tst.js:171:2:171:10 | base(url) |
|
||||
| tst.js:172:2:172:14 | variant1(url) |
|
||||
| tst.js:173:2:173:14 | variant2(url) |
|
||||
| tst.js:177:5:177:49 | $.get( ... a ) {}) |
|
||||
| tst.js:179:2:179:60 | $.getJS ... a ) {}) |
|
||||
| tst.js:181:2:181:69 | $.getSc ... r ) {}) |
|
||||
| tst.js:183:2:183:60 | $.post( ... ) { }) |
|
||||
| tst.js:185:2:185:60 | $( "#re ... lt) {}) |
|
||||
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) |
|
||||
| tst.js:195:2:195:54 | $.get( ... "json") |
|
||||
| tst.js:197:2:197:45 | $.ajax( ... blob"}) |
|
||||
| tst.js:200:2:200:21 | $.get("example.php") |
|
||||
| tst.js:202:5:208:7 | $.ajax( ... }}) |
|
||||
| tst.js:210:2:210:21 | $.get("example.php") |
|
||||
test_getADataNode
|
||||
| tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data |
|
||||
| tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 |
|
||||
@@ -74,13 +85,16 @@ test_getADataNode
|
||||
| tst.js:68:5:68:23 | superagent.get(url) | tst.js:68:52:68:60 | queryData |
|
||||
| tst.js:69:5:69:23 | superagent.get(url) | tst.js:69:48:69:56 | queryData |
|
||||
| tst.js:74:5:74:29 | $.ajax( ... data}) | tst.js:74:24:74:27 | data |
|
||||
| tst.js:77:5:77:32 | $.getJS ... data}) | tst.js:77:27:77:30 | data |
|
||||
| tst.js:77:5:77:32 | $.getJS ... data}) | tst.js:77:20:77:31 | {data: data} |
|
||||
| tst.js:80:15:80:34 | new XMLHttpRequest() | tst.js:82:14:82:17 | data |
|
||||
| tst.js:98:15:98:34 | new XMLHttpRequest() | tst.js:101:14:101:17 | data |
|
||||
| tst.js:106:16:106:35 | new XMLHttpRequest() | tst.js:108:15:108:18 | data |
|
||||
| tst.js:117:5:121:6 | request ... \\n }) | tst.js:117:18:121:5 | functio ... ;\\n } |
|
||||
| tst.js:123:5:127:6 | request ... \\n }) | tst.js:123:18:123:29 | {json: true} |
|
||||
| tst.js:129:5:129:37 | request ... true}) | tst.js:129:25:129:36 | {json: true} |
|
||||
| tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:31:179:38 | "MyData" |
|
||||
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:28:183:37 | "PostData" |
|
||||
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:190:11:190:20 | "AjaxData" |
|
||||
test_getHost
|
||||
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host |
|
||||
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host |
|
||||
@@ -121,11 +135,9 @@ test_getUrl
|
||||
| tst.js:68:5:68:23 | superagent.get(url) | tst.js:68:20:68:22 | url |
|
||||
| tst.js:69:5:69:23 | superagent.get(url) | tst.js:69:20:69:22 | url |
|
||||
| tst.js:74:5:74:29 | $.ajax( ... data}) | tst.js:74:12:74:14 | url |
|
||||
| tst.js:75:5:75:35 | $.ajax( ... data}) | tst.js:75:12:75:34 | {url: u ... : data} |
|
||||
| tst.js:75:5:75:35 | $.ajax( ... data}) | tst.js:75:18:75:20 | url |
|
||||
| tst.js:77:5:77:32 | $.getJS ... data}) | tst.js:77:15:77:17 | url |
|
||||
| tst.js:78:5:78:38 | $.getJS ... data}) | tst.js:78:15:78:37 | {url: u ... : data} |
|
||||
| tst.js:78:5:78:38 | $.getJS ... data}) | tst.js:78:21:78:23 | url |
|
||||
| tst.js:80:15:80:34 | new XMLHttpRequest() | tst.js:81:17:81:19 | url |
|
||||
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:14:87:24 | relativeUrl |
|
||||
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:11:89:22 | {host: host} |
|
||||
@@ -150,6 +162,17 @@ test_getUrl
|
||||
| tst.js:171:2:171:10 | base(url) | tst.js:171:7:171:9 | url |
|
||||
| tst.js:172:2:172:14 | variant1(url) | tst.js:172:11:172:13 | url |
|
||||
| tst.js:173:2:173:14 | variant2(url) | tst.js:173:11:173:13 | url |
|
||||
| tst.js:177:5:177:49 | $.get( ... a ) {}) | tst.js:177:12:177:27 | "ajax/test.html" |
|
||||
| tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:13:179:28 | "ajax/test.json" |
|
||||
| tst.js:181:2:181:69 | $.getSc ... r ) {}) | tst.js:181:15:181:28 | "ajax/test.js" |
|
||||
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:10:183:25 | "ajax/test.html" |
|
||||
| tst.js:185:2:185:60 | $( "#re ... lt) {}) | tst.js:185:23:185:38 | "ajax/test.html" |
|
||||
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:189:8:189:27 | "http://example.org" |
|
||||
| tst.js:195:2:195:54 | $.get( ... "json") | tst.js:195:9:195:24 | "ajax/test.json" |
|
||||
| tst.js:197:2:197:45 | $.ajax( ... blob"}) | tst.js:197:15:197:25 | "ajax/blob" |
|
||||
| tst.js:200:2:200:21 | $.get("example.php") | tst.js:200:8:200:20 | "example.php" |
|
||||
| tst.js:202:5:208:7 | $.ajax( ... }}) | tst.js:203:10:203:22 | "example.php" |
|
||||
| tst.js:210:2:210:21 | $.get("example.php") | tst.js:210:8:210:20 | "example.php" |
|
||||
test_getAResponseDataNode
|
||||
| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true |
|
||||
| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true |
|
||||
@@ -199,3 +222,14 @@ test_getAResponseDataNode
|
||||
| tst.js:151:5:151:23 | superagent.get(url) | tst.js:151:35:151:37 | res | stream | false |
|
||||
| tst.js:160:5:160:17 | xhr.send(url) | tst.js:162:9:162:29 | xhr.get ... eJson() | json | false |
|
||||
| tst.js:160:5:160:17 | xhr.send(url) | tst.js:163:9:163:32 | xhr.get ... aders() | headers | false |
|
||||
| tst.js:177:5:177:49 | $.get( ... a ) {}) | tst.js:177:40:177:43 | data | | false |
|
||||
| tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:51:179:54 | data | json | false |
|
||||
| tst.js:181:2:181:69 | $.getSc ... r ) {}) | tst.js:181:41:181:44 | data | text | false |
|
||||
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:50:183:53 | data | | false |
|
||||
| tst.js:185:2:185:60 | $( "#re ... lt) {}) | tst.js:185:50:185:55 | result | text | false |
|
||||
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:191:15:191:22 | ajaxData | json | false |
|
||||
| tst.js:195:2:195:54 | $.get( ... "json") | tst.js:195:37:195:40 | data | json | false |
|
||||
| tst.js:197:2:197:45 | $.ajax( ... blob"}) | tst.js:198:23:198:26 | data | blob | false |
|
||||
| tst.js:200:2:200:21 | $.get("example.php") | tst.js:200:37:200:44 | response | | false |
|
||||
| tst.js:202:5:208:7 | $.ajax( ... }}) | tst.js:207:21:207:36 | err.responseText | json | false |
|
||||
| tst.js:210:2:210:21 | $.get("example.php") | tst.js:210:55:210:70 | xhr.responseText | | false |
|
||||
|
||||
@@ -74,8 +74,8 @@ import {ClientRequest, net} from 'electron';
|
||||
$.ajax(url, {data: data});
|
||||
$.ajax({url: url, tdata: data});
|
||||
|
||||
$.getJSON(url, {data: data});
|
||||
$.getJSON({url: url, tdata: data});
|
||||
$.getJSON(url, {data: data}); // the entire "{data: data}" object is the data.
|
||||
$.getJSON({url: url, tdata: data}); // not how to use getJSON.
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(_, url);
|
||||
@@ -172,3 +172,40 @@ import {ClientRequest, net} from 'electron';
|
||||
variant1(url);
|
||||
variant2(url);
|
||||
});
|
||||
|
||||
(function() {
|
||||
$.get( "ajax/test.html", function( data ) {});
|
||||
|
||||
$.getJSON( "ajax/test.json", "MyData", function( data ) {});
|
||||
|
||||
$.getScript( "ajax/test.js", function( data, textStatus, jqxhr ) {});
|
||||
|
||||
$.post( "ajax/test.html", "PostData", function( data ) { });
|
||||
|
||||
$( "#result" ).load( "ajax/test.html", function(result) {});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "http://example.org",
|
||||
data: "AjaxData",
|
||||
success: (ajaxData) => {},
|
||||
dataType: "json"
|
||||
});
|
||||
|
||||
$.get( "ajax/test.json", function( data ) {}, "json");
|
||||
|
||||
$.ajax({url: "ajax/blob", dataType: "blob"})
|
||||
.done(function( data ) {});
|
||||
|
||||
$.get("example.php").done(function(response) {})
|
||||
|
||||
$.ajax({
|
||||
url: "example.php",
|
||||
type: 'POST',
|
||||
dataType: "json",
|
||||
error: function (err) {
|
||||
console.log(err.responseText)
|
||||
}});
|
||||
|
||||
$.get("example.php").fail(function(xhr) {console.log(xhr.responseText)});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
getPathArgument
|
||||
| file-access.js:3:1:4:34 | fstream ... file"}) | file-access.js:4:19:4:32 | "path/to/file" |
|
||||
| file-access.js:8:1:9:24 | fstream ... o/dir") | file-access.js:9:11:9:23 | "path/to/dir" |
|
||||
| file-access.js:10:9:10:43 | fstream ... r/dir") | file-access.js:10:24:10:42 | "path/to/other/dir" |
|
||||
| file-access.js:15:1:15:60 | writeFi ... rr) {}) | file-access.js:15:17:15:28 | 'atmoic.txt' |
|
||||
| file-access.js:18:1:18:59 | writeFi ... tions]) | file-access.js:18:21:18:34 | "syncFile.txt" |
|
||||
| file-access.js:22:1:22:48 | recursi ... es) {}) | file-access.js:22:11:22:21 | "some/path" |
|
||||
| file-access.js:25:1:25:59 | jsonfil ... bj) {}) | file-access.js:25:19:25:34 | '/tmp/data.json' |
|
||||
| file-access.js:26:1:26:39 | jsonfil ... .json') | file-access.js:26:23:26:38 | '/tmp/data.json' |
|
||||
| file-access.js:28:1:28:60 | jsonfil ... rr) {}) | file-access.js:28:20:28:35 | '/tmp/data.json' |
|
||||
| file-access.js:29:1:29:45 | jsonfil ... ', obj) | file-access.js:29:24:29:39 | '/tmp/data.json' |
|
||||
| file-access.js:34:4:34:23 | pathExists('foo.js') | file-access.js:34:15:34:22 | 'foo.js' |
|
||||
| file-access.js:39:1:39:28 | rimraf( ... => {}) | file-access.js:39:8:39:10 | "/" |
|
||||
| file-access.js:42:1:42:59 | dir.rea ... on(){}) | file-access.js:42:15:42:31 | "/some/directory" |
|
||||
| file-access.js:46:1:46:25 | vfs.src ... path"]) | file-access.js:46:9:46:24 | ["some", "path"] |
|
||||
| file-access.js:47:1:47:36 | vfs.des ... true }) | file-access.js:47:10:47:13 | './' |
|
||||
| file-access.js:51:1:51:36 | ncp("fr ... rr) {}) | file-access.js:51:5:51:10 | "from" |
|
||||
| file-access.js:51:1:51:36 | ncp("fr ... rr) {}) | file-access.js:51:13:51:16 | "to" |
|
||||
getReadNode
|
||||
| file-access.js:25:1:25:59 | jsonfil ... bj) {}) | file-access.js:25:52:25:54 | obj |
|
||||
| file-access.js:26:1:26:39 | jsonfil ... .json') | file-access.js:26:1:26:39 | jsonfil ... .json') |
|
||||
getWriteNode
|
||||
| file-access.js:15:1:15:60 | writeFi ... rr) {}) | file-access.js:15:31:15:36 | 'Data' |
|
||||
| file-access.js:18:1:18:59 | writeFi ... tions]) | file-access.js:18:37:18:47 | "More data" |
|
||||
| file-access.js:28:1:28:60 | jsonfil ... rr) {}) | file-access.js:28:38:28:40 | obj |
|
||||
| file-access.js:29:1:29:45 | jsonfil ... ', obj) | file-access.js:29:42:29:44 | obj |
|
||||
@@ -0,0 +1,7 @@
|
||||
import javascript
|
||||
|
||||
query DataFlow::Node getPathArgument(FileSystemAccess access) { result = access.getAPathArgument() }
|
||||
|
||||
query DataFlow::Node getReadNode(FileSystemReadAccess access) { result = access.getADataNode() }
|
||||
|
||||
query DataFlow::Node getWriteNode(FileSystemWriteAccess access) { result = access.getADataNode() }
|
||||
@@ -1,3 +1,4 @@
|
||||
| file-access.js:22:39:22:43 | files |
|
||||
| tst-file-names.js:7:1:7:10 | walkSync() |
|
||||
| tst-file-names.js:9:35:9:44 | stats.name |
|
||||
| tst-file-names.js:11:1:11:12 | glob.sync(_) |
|
||||
@@ -10,3 +11,7 @@
|
||||
| tst-file-names.js:25:18:25:22 | files |
|
||||
| tst-file-names.js:27:24:27:28 | files |
|
||||
| tst-file-names.js:29:27:29:30 | file |
|
||||
| tst-file-names.js:32:34:32:38 | files |
|
||||
| tst-file-names.js:34:15:34:29 | await globby(_) |
|
||||
| tst-file-names.js:36:16:36:38 | await f ... sync(_) |
|
||||
| tst-file-names.js:38:16:38:57 | await f ... => {}) |
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
var fstream = require("fstream");
|
||||
|
||||
fstream
|
||||
.Writer({ path: "path/to/file"})
|
||||
.write("hello\n")
|
||||
.end()
|
||||
|
||||
fstream
|
||||
.Reader("path/to/dir")
|
||||
.pipe(fstream.Writer("path/to/other/dir"))
|
||||
|
||||
|
||||
var writeFileAtomic= require("write-file-atomic");
|
||||
|
||||
writeFileAtomic('atmoic.txt', 'Data', {}, function (err) {});
|
||||
|
||||
var writeFileAtomicSync = require('write-file-atomic').sync
|
||||
writeFileAtomicSync("syncFile.txt", "More data", [options])
|
||||
|
||||
var recursive = require("recursive-readdir");
|
||||
|
||||
recursive("some/path", function (err, files) {});
|
||||
|
||||
const jsonfile = require('jsonfile');
|
||||
jsonfile.readFile('/tmp/data.json', function (err, obj) {});
|
||||
jsonfile.readFileSync('/tmp/data.json');
|
||||
|
||||
jsonfile.writeFile('/tmp/data.json', obj, function (err) {});
|
||||
jsonfile.writeFileSync('/tmp/data.json', obj);
|
||||
|
||||
|
||||
const pathExists = require('path-exists');
|
||||
|
||||
if(pathExists('foo.js')) {
|
||||
// do something.
|
||||
}
|
||||
|
||||
var rimraf = require("rimraf");
|
||||
rimraf("/", {}, (err) => {});
|
||||
|
||||
var dir = require("node-dir");
|
||||
dir.readFiles("/some/directory",function() {},function(){});
|
||||
|
||||
var vfs = require("vinyl-fs");
|
||||
|
||||
vfs.src(["some", "path"]);
|
||||
vfs.dest('./', { sourcemaps: true });
|
||||
|
||||
|
||||
var ncp = require('ncp').ncp;
|
||||
ncp("from", "to", function (err) {});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user