Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix crash when this is typed as a generic T with no type constraints #47991

Merged
merged 6 commits into from
Mar 5, 2022

Conversation

jihndai
Copy link
Contributor

@jihndai jihndai commented Feb 22, 2022

Fixes #47965

While checking a protected property's accessibility, the crash occurs because getConstraintOfTypeParameter can return undefined when this is typed as the generic T and T has no type constraints on it. The fix for this was to check for undefined and refuse access if it was, as we can't guarantee that an instantiation of a generic T can access protected properties when T isn't constrained to any class/type.

Additionally, the explicit this type was also skipping through derivation checks done by isClassDerivedFromDeclaringClasses, which led to some inconsistent behavior shown below, so changes were also made to address this, but please do correct me if I'm mistaken.

class D {
  derived1(val: D1) {
    val.d1(); // currently shows an error, as expected
  }
  derived1ThisD(this: D, val: D1) {
    val.d1(); // should show the same error as above, but doesn't
  }
}
class D1 extends D {
  protected d1() {}
}

function thisString(this: string, d: D1) {
  /**
   * Error message seems misleading:
   * 
   * "Property 'd1' is protected and only accessible through an instance of class 'any'. This is an instance of class 'D1'"
   */
  d.d1();
}

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Feb 22, 2022
@jihndai jihndai changed the title Fix crash when generic T as assigned to this with no constraints Fix crash when this is typed as a generic T with no type constraints Feb 24, 2022
@typescript-bot typescript-bot added For Backlog Bug PRs that fix a backlog bug and removed For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Feb 24, 2022
@jihndai

This comment was marked as resolved.

@jihndai
Copy link
Contributor Author

jihndai commented Feb 25, 2022

Nevermind, found the issues talking about private's behavior and protected's behavior in relation to this.

@jihndai jihndai force-pushed the fix/47965 branch 2 times, most recently from 176a44a to 87ad30b Compare March 1, 2022 20:34
@sandersn sandersn self-assigned this Mar 3, 2022
@sandersn sandersn requested review from sandersn and gabritto March 3, 2022 17:41
Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

I think the basic fixes are good. Just some minor style requests.

@@ -28251,14 +28251,17 @@ namespace ts {
// of the property as base classes
let enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => {
const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!) as InterfaceType;
return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing) ? enclosingClass : undefined;
return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing);
Copy link
Member

Choose a reason for hiding this comment

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

This looks like an unrelated driveby cleanup, not part of either fix. Is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

correct just a cleanup, shouldn't change the behavior at all.

return undefined;
}

function getThisTypeFromNodeContext(node: Node) {
Copy link
Member

Choose a reason for hiding this comment

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

I think I would inline this since it's only two lines and only called once.

Comment on lines 28260 to 28264
if (
flags & ModifierFlags.Static
|| !(enclosingClass = getEnclosingClassFromThisParameter(location))
|| !(enclosingClass = isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing))
) {
Copy link
Member

Choose a reason for hiding this comment

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

If it's OK to run the checks even for static methods (and I think it is), then the below is much less surprising to read:

Suggested change
if (
flags & ModifierFlags.Static
|| !(enclosingClass = getEnclosingClassFromThisParameter(location))
|| !(enclosingClass = isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing))
) {
enclosingClass = getEnclosingClassFromThisParameter(location)) || isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing);
if (flags & ModifierFlags.Static || !enclosingClass) {

Copy link
Contributor Author

@jihndai jihndai Mar 3, 2022

Choose a reason for hiding this comment

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

Yeah should be ok, at best it was just a very minor optimization to avoid fetching this's parameter if the field was already static.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

getEnclosingClassFromThisParameter can return undefined while isClassDerivedFromDeclaringClasses doesn't accept undefined, so I'll have to do some type checking with this style instead:

enclosingClass = getEnclosingClassFromThisParameter(location);
enclosingClass = enclosingClass && isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing);
if (flags & ModifierFlags.Static || !enclosingClass) {

Copy link
Contributor Author

@jihndai jihndai Mar 3, 2022

Choose a reason for hiding this comment

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

Also noticed that the name isClassDerivedFromDeclaringClasses may be a bit misleading, as it doesn't return a boolean, but instead it returns the first parameter if checks succeeds, otherwise it returns undefined.

Not sure if we'd want to change the name or not.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe as a follow-up, I'd rather get this change in.

@@ -28290,6 +28290,25 @@ namespace ts {
return true;
}

function getEnclosingClassFromThisParameter(node: Node): InterfaceType | undefined {
let thisType = getThisTypeFromNodeContext(node);

Copy link
Member

Choose a reason for hiding this comment

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

extremely minor style preference: I think the blank lines in this function should be deleted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Crash checking protected method accessibility: Cannot read property 'target' of undefined
3 participants