From ac78352ad2111f961b6ddbbc145e0dcc0d8b44f5 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 21 Nov 2024 19:25:51 +0100 Subject: [PATCH] Forbid `super` in static class methods with server function directives Static class methods that are annotated with `"use cache"` or `"use server"` must not call `super`. --- .../src/transforms/server_actions.rs | 39 +++++++++++++++++++ .../server-actions/server-graph/26/input.js | 9 ++++- .../server-actions/server-graph/26/output.js | 5 ++- .../server-graph/26/output.stderr | 24 ++++++++---- 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/crates/next-custom-transforms/src/transforms/server_actions.rs b/crates/next-custom-transforms/src/transforms/server_actions.rs index 48e937567aa18..aca27d523c3d4 100644 --- a/crates/next-custom-transforms/src/transforms/server_actions.rs +++ b/crates/next-custom-transforms/src/transforms/server_actions.rs @@ -2145,6 +2145,16 @@ impl VisitMut for ServerActions { } } + fn visit_mut_super(&mut self, n: &mut Super) { + if let ThisStatus::Forbidden { directive } = &self.this_status { + emit_error(ServerActionsErrorKind::ForbiddenExpression { + span: n.span, + expr: "super".into(), + directive: directive.clone(), + }); + } + } + fn visit_mut_ident(&mut self, n: &mut Ident) { if n.sym == *"arguments" { if let ThisStatus::Forbidden { directive } = &self.this_status { @@ -2745,6 +2755,35 @@ impl ClosureReplacer<'_> { } impl VisitMut for ClosureReplacer<'_> { + fn visit_mut_stmt(&mut self, stmt: &mut Stmt) { + stmt.visit_mut_children_with(self); + + // Replace super calls in hoisted static class methods with empty + // statements so that we can remove them later to ensure that the + // resulting JS is still valid. Before this, we have already emitted an + // error for the super call. + if let Stmt::Expr(ExprStmt { + expr: + box Expr::Call(CallExpr { + callee: Callee::Expr(box Expr::SuperProp(SuperPropExpr { .. })), + .. + }), + .. + }) = stmt + { + stmt.take(); + } + } + + fn visit_mut_stmts(&mut self, stmts: &mut Vec) { + stmts.visit_mut_children_with(self); + + stmts.retain(|stmt| { + // Remove empty statements. + !matches!(stmt, Stmt::Empty(..)) + }); + } + fn visit_mut_expr(&mut self, e: &mut Expr) { e.visit_mut_children_with(self); diff --git a/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/input.js b/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/input.js index 6996bf0752c93..ab9d632bf687e 100644 --- a/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/input.js +++ b/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/input.js @@ -1,10 +1,17 @@ -export class MyClass { +import { BaseClass } from './base' + +export class MyClass extends BaseClass { static async foo() { + // super is allowed here + super.foo() + return fetch('https://example.com').then((res) => res.json()) } static async bar() { 'use cache' + // super is not allowed here + super.bar() // arguments is not allowed here console.log(arguments) // this is not allowed here diff --git a/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/output.js b/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/output.js index bc52378465ff1..5d7bfd6645ea1 100644 --- a/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/output.js +++ b/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/output.js @@ -1,6 +1,7 @@ /* __next_internal_action_entry_do_not_use__ {"803128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; +import { BaseClass } from './base'; export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, /*#__TURBOPACK_DISABLE_EXPORT_MERGING__*/ async function bar() { // arguments is not allowed here console.log(arguments); @@ -11,8 +12,10 @@ Object.defineProperty($$RSC_SERVER_CACHE_0, "name", { "value": "bar", "writable": false }); -export class MyClass { +export class MyClass extends BaseClass { static async foo() { + // super is allowed here + super.foo(); return fetch('https://example.com').then((res)=>res.json()); } static bar = registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null); diff --git a/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/output.stderr b/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/output.stderr index d78e16e543aa4..f5e5172c422f4 100644 --- a/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/output.stderr +++ b/crates/next-custom-transforms/tests/errors/server-actions/server-graph/26/output.stderr @@ -1,16 +1,24 @@ + x "use cache" functions can not use `super`. + | + ,-[input.js:14:1] + 13 | // super is not allowed here + 14 | super.bar() + : ^^^^^ + 15 | // arguments is not allowed here + `---- x "use cache" functions can not use `arguments`. | - ,-[input.js:9:1] - 8 | // arguments is not allowed here - 9 | console.log(arguments) + ,-[input.js:16:1] + 15 | // arguments is not allowed here + 16 | console.log(arguments) : ^^^^^^^^^ - 10 | // this is not allowed here + 17 | // this is not allowed here `---- x "use cache" functions can not use `this`. | - ,-[input.js:11:1] - 10 | // this is not allowed here - 11 | return this.foo() + ,-[input.js:18:1] + 17 | // this is not allowed here + 18 | return this.foo() : ^^^^ - 12 | } + 19 | } `----