diff --git a/CHANGELOG.md b/CHANGELOG.md index eec216086..447cb7580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ -## 1.88.1-dev +## 1.88.0-dev * Fix a bug when calculating source spans for interpolations. -## 1.88.0 +### Dart and JS APIs + +* **Potentially breaking bug fix:** Throw an error when passing a function or + mixin object from one compilation to another. + +### Dart API * Deprecate passing a relative URL to `compileString()` and related functions. diff --git a/lib/src/value/function.dart b/lib/src/value/function.dart index a5f949550..31ab6fe1b 100644 --- a/lib/src/value/function.dart +++ b/lib/src/value/function.dart @@ -5,6 +5,7 @@ import 'package:meta/meta.dart'; import '../callable.dart'; +import '../exception.dart'; import '../visitor/interface/value.dart'; import '../value.dart'; @@ -23,7 +24,16 @@ class SassFunction extends Value { /// synchronous evaluate visitor will crash if this isn't a [Callable]. final AsyncCallable callable; - SassFunction(this.callable); + /// The unique compile context for tracking if this [SassFunction] belongs to + /// the current compilation or not. + /// + /// This is `null` for functions defined in plugins' Dart code. + final Object? _compileContext; + + SassFunction(this.callable) : _compileContext = null; + + @internal + SassFunction.withCompileContext(this.callable, this._compileContext); /// @nodoc @internal @@ -31,6 +41,20 @@ class SassFunction extends Value { SassFunction assertFunction([String? name]) => this; + /// Asserts that this SassFunction belongs to [compileContext] and returns it. + /// + /// It's checked before evaluating a SassFunction to prevent execution of + /// SassFunction across different compilations. + @internal + SassFunction assertCompileContext(Object compileContext) { + if (_compileContext != null && _compileContext != compileContext) { + throw SassScriptException( + "$this does not belong to current compilation."); + } + + return this; + } + bool operator ==(Object other) => other is SassFunction && callable == other.callable; diff --git a/lib/src/value/mixin.dart b/lib/src/value/mixin.dart index 79091579d..99b50f187 100644 --- a/lib/src/value/mixin.dart +++ b/lib/src/value/mixin.dart @@ -5,6 +5,7 @@ import 'package:meta/meta.dart'; import '../callable.dart'; +import '../exception.dart'; import '../visitor/interface/value.dart'; import '../value.dart'; @@ -25,7 +26,14 @@ final class SassMixin extends Value { @internal final AsyncCallable callable; - SassMixin(this.callable); + /// The unique compile context for tracking if this [SassMixin] belongs to the + /// current compilation or not. + final Object? _compileContext; + + SassMixin(this.callable) : _compileContext = null; + + @internal + SassMixin.withCompileContext(this.callable, this._compileContext); /// @nodoc @internal @@ -33,6 +41,20 @@ final class SassMixin extends Value { SassMixin assertMixin([String? name]) => this; + /// Asserts that this SassMixin belongs to [compileContext] and returns it. + /// + /// It's checked before evaluating a SassMixin to prevent execution of + /// SassMixin across different compilations. + @internal + SassMixin assertCompileContext(Object compileContext) { + if (_compileContext != null && _compileContext != compileContext) { + throw SassScriptException( + "$this does not belong to current compilation."); + } + + return this; + } + bool operator ==(Object other) => other is SassMixin && callable == other.callable; diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 4540ee68c..617ffc712 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -179,6 +179,10 @@ final class _EvaluateVisitor /// Whether to track source map information. final bool _sourceMap; + /// The unique compile context for tracking if [SassFunction]s and + /// [SassMixin]s belongs to the current compilation or not. + final Object _compileContext = Object(); + /// The current lexical environment. AsyncEnvironment _environment; @@ -433,7 +437,8 @@ final class _EvaluateVisitor return SassMap({ for (var (name, value) in module.functions.pairs) - SassString(name): SassFunction(value), + SassString(name): + SassFunction.withCompileContext(value, _compileContext), }); }, url: "sass:meta"), @@ -446,7 +451,8 @@ final class _EvaluateVisitor return SassMap({ for (var (name, value) in module.mixins.pairs) - SassString(name): SassMixin(value), + SassString(name): + SassMixin.withCompileContext(value, _compileContext), }); }, url: "sass:meta"), @@ -462,7 +468,8 @@ final class _EvaluateVisitor if (module != null) { throw r"$css and $module may not both be passed at once."; } - return SassFunction(PlainCssCallable(name.text)); + return SassFunction.withCompileContext( + PlainCssCallable(name.text), _compileContext); } var callable = _addExceptionSpan(_callableNode!, () { @@ -477,7 +484,7 @@ final class _EvaluateVisitor }); if (callable == null) throw "Function not found: $name"; - return SassFunction(callable); + return SassFunction.withCompileContext(callable, _compileContext); }, url: "sass:meta", ), @@ -497,7 +504,7 @@ final class _EvaluateVisitor ); if (callable == null) throw "Mixin not found: $name"; - return SassMixin(callable); + return SassMixin.withCompileContext(callable, _compileContext); }, url: "sass:meta"), AsyncBuiltInCallable.function("call", r"$function, $args...", ( @@ -541,7 +548,10 @@ final class _EvaluateVisitor return await expression.accept(this); } - var callable = function.assertFunction("function").callable; + var callable = function + .assertFunction("function") + .assertCompileContext(_compileContext) + .callable; // ignore: unnecessary_type_check if (callable is AsyncCallable) { return await _runFunctionCallable( @@ -608,7 +618,10 @@ final class _EvaluateVisitor rest: ValueExpression(args, callableNode.span), ); - var callable = mixin.assertMixin("mixin").callable; + var callable = mixin + .assertMixin("mixin") + .assertCompileContext(_compileContext) + .callable; var content = _environment.content; // ignore: unnecessary_type_check diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 491165efc..5e126d91e 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 05d8589b401932198e1f52434066ea4d6cbf3756 +// Checksum: 6e5710daa106ed0b9b684af8bc61ce9cc233a10b // // ignore_for_file: unused_import @@ -187,6 +187,10 @@ final class _EvaluateVisitor /// Whether to track source map information. final bool _sourceMap; + /// The unique compile context for tracking if [SassFunction]s and + /// [SassMixin]s belongs to the current compilation or not. + final Object _compileContext = Object(); + /// The current lexical environment. Environment _environment; @@ -441,7 +445,8 @@ final class _EvaluateVisitor return SassMap({ for (var (name, value) in module.functions.pairs) - SassString(name): SassFunction(value), + SassString(name): + SassFunction.withCompileContext(value, _compileContext), }); }, url: "sass:meta"), @@ -454,7 +459,8 @@ final class _EvaluateVisitor return SassMap({ for (var (name, value) in module.mixins.pairs) - SassString(name): SassMixin(value), + SassString(name): + SassMixin.withCompileContext(value, _compileContext), }); }, url: "sass:meta"), @@ -470,7 +476,8 @@ final class _EvaluateVisitor if (module != null) { throw r"$css and $module may not both be passed at once."; } - return SassFunction(PlainCssCallable(name.text)); + return SassFunction.withCompileContext( + PlainCssCallable(name.text), _compileContext); } var callable = _addExceptionSpan(_callableNode!, () { @@ -485,7 +492,7 @@ final class _EvaluateVisitor }); if (callable == null) throw "Function not found: $name"; - return SassFunction(callable); + return SassFunction.withCompileContext(callable, _compileContext); }, url: "sass:meta", ), @@ -505,7 +512,7 @@ final class _EvaluateVisitor ); if (callable == null) throw "Mixin not found: $name"; - return SassMixin(callable); + return SassMixin.withCompileContext(callable, _compileContext); }, url: "sass:meta"), BuiltInCallable.function("call", r"$function, $args...", ( @@ -549,7 +556,10 @@ final class _EvaluateVisitor return expression.accept(this); } - var callable = function.assertFunction("function").callable; + var callable = function + .assertFunction("function") + .assertCompileContext(_compileContext) + .callable; // ignore: unnecessary_type_check if (callable is Callable) { return _runFunctionCallable( @@ -616,7 +626,10 @@ final class _EvaluateVisitor rest: ValueExpression(args, callableNode.span), ); - var callable = mixin.assertMixin("mixin").callable; + var callable = mixin + .assertMixin("mixin") + .assertCompileContext(_compileContext) + .callable; var content = _environment.content; // ignore: unnecessary_type_check diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index 5802e4112..4b7430f33 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,8 +1,4 @@ -## 15.5.1-dev - -* No user-visible changes. - -## 15.5.0 +## 15.5.0-dev * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index b01342ace..44cafd4b8 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -2,7 +2,7 @@ name: sass_api # Note: Every time we add a new Sass AST node, we need to bump the *major* # version because it's a breaking change for anyone who's implementing the # visitor interface(s). -version: 15.5.1-dev +version: 15.5.0-dev description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: ">=3.6.0 <4.0.0" dependencies: - sass: 1.88.1 + sass: 1.88.0 dev_dependencies: dartdoc: ^8.0.14 diff --git a/pubspec.yaml b/pubspec.yaml index 0da8832a8..e67c1c6b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.88.1-dev +version: 1.88.0-dev description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass