blob: f6aadcd363d76e3c664da9a90afc0202bf81504f [file]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:analyzer/src/error/listener.dart';
/// A verifier that checks for unsafe Unicode text.
/// See: https://nvd.nist.gov/vuln/detail/CVE-2021-22567
class UnicodeTextVerifier {
final DiagnosticReporter _diagnosticReporter;
UnicodeTextVerifier(this._diagnosticReporter);
void verify(CompilationUnit unit, String source) {
// Optimization: We assume that we won't find any of these. Doing the loop
// in a method that isn't inlined currently reduces the instruction cost per
// character from 22 to 12.
if (!_hasMatch(source)) return;
for (var offset = 0; offset < source.length; ++offset) {
var codeUnit = source.codeUnitAt(offset);
if (_isCodeUnitMatch(codeUnit)) {
var node = unit.nodeCovering(offset: offset);
// If it's not in a string literal, we assume we're in a comment.
// This can potentially over-report on syntactically incorrect sources
// (where Unicode is outside a string or comment).
var diagnosticCode =
node is SimpleStringLiteral || node is InterpolationString
? diag.textDirectionCodePointInLiteral
: diag.textDirectionCodePointInComment;
var code = codeUnit.toRadixString(16).toUpperCase();
_diagnosticReporter.report(
diagnosticCode
.withArguments(codePoint: code)
.atOffset(offset: offset, length: 1),
);
}
}
}
// Optimization: Specifically never inline this to make sure not to introduce
// extra instructions into the right loop.
@pragma("vm:never-inline")
bool _hasMatch(String source) {
for (int offset = 0; offset < source.length; ++offset) {
var codeUnit = source.codeUnitAt(offset);
if (_isCodeUnitMatch(codeUnit)) {
return true;
}
}
return false;
}
// Optimization: We want this inlined to avoid the overhead of doing a call,
// and we do this to avoid duplicating the code.
@pragma("vm:prefer-inline")
bool _isCodeUnitMatch(int codeUnit) {
// U+202A, U+202B, U+202C, U+202D, U+202E, U+2066, U+2067, U+2068, U+2069.
if (0x202a <= codeUnit &&
codeUnit <= 0x2069 &&
(codeUnit <= 0x202e || 0x2066 <= codeUnit)) {
return true;
}
return false;
}
}