Skip to content

Conversation

@asger-semmle
Copy link
Contributor

Fixes a technically correct but seemingly unfixable alert in type-checked JS code, like this:

class C extends Q {
  constructor() {
    super(...);
    initStuff();

    /**
     * An important field initialized somewhere else.
     * @type { string }
     */
    this.x = this.x;
  }
}

TypeScript can check JS code, and recognizes JSDoc-annotated assignments in the constructor as fields. If these fields are initialized elsewhere, however, a self-assignment may be necessary to have a place to "declare" the field.

I still think it's fishy, but I think it's fairly safe to turn it off in this case. The real problems are unlikely to have a JSDoc comment in front of them.

@asger-semmle asger-semmle requested a review from a team as a code owner September 6, 2018 16:09
Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree about the fishyness,
I think we should tie the heuristic as close as possible to the specific TypeScript use case, see comments.

not isDOMProperty(e.(PropAccess).getPropertyName())
select e.getParent(), "This expression assigns " + dsc + " to itself." No newline at end of file
not isDOMProperty(e.(PropAccess).getPropertyName()) and
// exclude self-assignments with a JSDoc comment
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps:

      // exclude self-assignments that have been inserted to satisfy the TypeScript type checker

select e.getParent(), "This expression assigns " + dsc + " to itself." No newline at end of file
not isDOMProperty(e.(PropAccess).getPropertyName()) and
// exclude self-assignments with a JSDoc comment
not exists(e.getAssignment().getParent().(ExprStmt).getDocumentation().getATag())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this even more specific?

      not e.getAssignment().getParent().(ExprStmt).getDocumentation().getATag().getName() = "type"

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great.
I think this deserves a change note, it may be a more widespread problem than we are aware of.

@Zarel
Copy link

Zarel commented Sep 7, 2018

Modern versions of TypeScript also support

    /**
     * An important field initialized somewhere else.
     * @type { string }
     */
    this.x;

Which is probably clearer.

@asger-semmle
Copy link
Contributor Author

That's a very good point @Zarel, thank you much for bringing it up.

That case falls under the purview of the "expression has no effect" query, which already has the same exception for JSDoc comments. Maybe it's worth reconsidering the exception here, though, since there actually is a more reasonable alternative (@esben-semmle).

@Zarel was there a reason you didn't use the short form in Pokemon-Showdown?

@Zarel
Copy link

Zarel commented Sep 10, 2018

@asger-semmle We started using TypeScript's checkJs support when it was very experimental, and didn't support that form. TypeScript's checkJs support is honestly still pretty bad, and I'm probably going to need to transition to using .ts files at some point.

I'll probably switch to using the shorter form at some point.

@ghost
Copy link

ghost commented Sep 10, 2018

Maybe it's worth reconsidering the exception here, though, since there actually is a more reasonable alternative (@esben-semmle).

I agree. Let us keep the whitelist simple, and drop this PR.

@ghost ghost closed this Sep 10, 2018
@Zarel
Copy link

Zarel commented Oct 28, 2018

Someone mentioned one reason to do it this way.

If you use

this.x = this.x;

then the property will be initialized to undefined if it doesn't already exist. This will make V8's JIT optimize access to it, but if you don't, every access will be deoptimized.

@xiemaisi
Copy link

True, but this.x = undefined; achieves the same effect and is arguably clearer.

@Zarel
Copy link

Zarel commented Oct 28, 2018

this.x = undefined will set this.x to undefined always. The use-case is to set this.x to undefined only when it doesn't already exist.

The use-case is something like:

class Foo {
  constructor(options = {}) {
    Object.assign(this, options);
    /** @type {string | undefined} */
    this.x = this.x;
  }
}
let a = new Foo();
let b = new Foo({x: "bar"});

@xiemaisi
Copy link

Right, fair enough. If people feel that this is a common enough idiom to whitelist it, we can do so.

@Zarel
Copy link

Zarel commented Oct 28, 2018

I honestly wouldn't know. I would guess it's really uncommon. I'm using it to strongly type the data I get from data files, but there's probably a better way to do it.

@xiemaisi xiemaisi added the JS label Oct 29, 2018
@xiemaisi
Copy link

OK, that sounds like something people might reasonably do. @asger-semmle, @esben-semmle, do you think it is safe to whitelist this.prop = this.prop assignments or might we lose too many interesting results that way?

@ghost
Copy link

ghost commented Oct 29, 2018

I am not sure.
At the very least, we should restrict the whitelist to be for constructors.
My main fear is that the presence of another this.propA = o.propA assignment in the constructor may indicate that we are hiding a copy/paste error, unfortunately that is a wobbly heuristic to de-whitelist on.

@xiemaisi
Copy link

OK. How about resurrecting this PR with its idea to only whitelist self-assignments that have a @type annotation, potentially even restricting it to self-assignments on this? We should then also add an explanation of his whitelisting to the qhelp.

@asger-semmle asger-semmle reopened this Oct 29, 2018
@asger-semmle
Copy link
Contributor Author

The @type jsdoc comment seems like a safe enough whitelisting to me.

@asger-semmle asger-semmle force-pushed the documentable-self-assign branch from 8a5f7f6 to 5bd96f7 Compare October 29, 2018 15:32
@xiemaisi
Copy link

LGTM, but could you also update the qhelp to mention this additional whitelisting condition, perhaps in the "how to fix" section? It gives people a semantic way of addressing the alert without having to resort to suppression comments.

xiemaisi
xiemaisi previously approved these changes Oct 31, 2018
@asger-semmle
Copy link
Contributor Author

Will rebase after #388 lands, as they both touch the change notes.

@xiemaisi
Copy link

xiemaisi commented Nov 5, 2018

#388 has landed; time for rebase?

@asger-semmle asger-semmle force-pushed the documentable-self-assign branch from 73de56e to e670919 Compare November 5, 2018 11:31
@asger-semmle
Copy link
Contributor Author

Rebased

xiemaisi
xiemaisi previously approved these changes Nov 5, 2018
Copy link

@xiemaisi xiemaisi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, modulo one niggle which I won't insist on.

</p>

<p>
If the self-assignment is intentional, and is needed for documentation or optimization purposes,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spurious comma before "and".

@semmle-qlci semmle-qlci merged commit 2457eb9 into github:master Nov 7, 2018
aibaars added a commit that referenced this pull request Oct 14, 2021
Minimal implementation of shared type-tracking library
smowton added a commit to smowton/codeql that referenced this pull request Jan 17, 2022
…tution-prototypes

Extract generic method prototypes
MathiasVP pushed a commit to MathiasVP/ql that referenced this pull request Aug 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants