All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.shapesecurity.shift.validator.Validator Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
/*
 * Copyright 2014 Shape Security, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.shapesecurity.shift.validator;

import com.shapesecurity.functional.data.ImmutableList;
import com.shapesecurity.functional.data.Maybe;
import com.shapesecurity.shift.ast.*;
import com.shapesecurity.shift.parser.JsError;
import com.shapesecurity.shift.parser.Token;
import com.shapesecurity.shift.parser.Tokenizer;
import com.shapesecurity.shift.parser.token.EOFToken;
import com.shapesecurity.shift.parser.token.StringLiteralToken;
import com.shapesecurity.shift.utils.Utils;
import com.shapesecurity.shift.visitor.Director;
import com.shapesecurity.shift.visitor.MonoidalReducer;

import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Validator extends MonoidalReducer {

    public Validator() {
        super(ValidationContext.MONOID);
    }

    private static boolean checkIsLiteralRegExpPattern(String pattern) {
        // copied from tokenizer for getting a regex token
        int index = 0;
        boolean terminated = false;
        boolean classMarker = false;
        while (index < pattern.length()) {
            char ch = pattern.charAt(index);
            if (ch == '\\') {
                index++;
                ch = pattern.charAt(index);
                if (Utils.isLineTerminator(ch)) {
                    return false;
                }
                index++;
            } else if (Utils.isLineTerminator(ch)) {
                return false;
            } else {
                if (classMarker) {
                    if (ch == ']') {
                        classMarker = false;
                    }
                } else {
                    if (ch == '/') {
                        terminated = true;
                        index++;
                        break;
                    } else if (ch == '[') {
                        classMarker = true;
                    }
                }
                index++;
            }
        }

        if (!terminated) {
            return false;
        }

        while (index < pattern.length()) {
            char ch = pattern.charAt(index);
            if (ch == '\\') {
                return false;
            }
            if (!Utils.isIdentifierPart(ch)) {
                break;
            }
            index++;
        }
        return true;
    }

    public static boolean checkIsValidIdentifierName(String name) {
        return name.length() > 0 && Utils.isIdentifierStart(name.charAt(0)) && name.chars().allMatch(Utils::isIdentifierPart);
    }

    public static ImmutableList validate(Script script) {
        List errors = Director.reduceScript(new Validator(), script).errors;
        return ImmutableList.from(Director.reduceScript(new Validator(), script).errors);
    }

    public static ImmutableList validate(Module module) {
        List errors = Director.reduceModule(new Validator(), module).errors;
        return ImmutableList.from(Director.reduceModule(new Validator(), module).errors);
    }

    private boolean checkIsStringLiteral(String rawValue) {
        Tokenizer tokenizer;
        try {
            tokenizer = new Tokenizer("\'" + rawValue + "\'", false);
            Token token = tokenizer.lookahead;
            if (!(token instanceof StringLiteralToken)) {
                System.out.println("NOT STRING LITERAL");
                return false;
            }
            token = tokenizer.collectToken();
            if (!(token instanceof EOFToken)) {
                return false;
            }
        } catch (JsError jsError) {
            try {
                tokenizer = new Tokenizer("\"" + rawValue + "\"", false);
                Token token = tokenizer.lookahead;
                if (!(token instanceof StringLiteralToken)) {
                    System.out.println("NOT STRING LITERAL");
                    return false;
                }
                token = tokenizer.collectToken();
                if (!(token instanceof EOFToken)) {
                    return false;
                }
            } catch (JsError jsError1) {
                return false;
            }
        }
        return true;
    }

    private boolean isProblematicIfStatement(IfStatement node) {
        if (node.alternate.isNothing()) {
            return false;
        }
        Maybe current = Maybe.just(node.consequent);
        do {
            Statement currentStmt = current.just();
            if (currentStmt instanceof IfStatement && ((IfStatement) currentStmt).alternate.isNothing()) {
                return true;
            }
            current = trailingStatement(currentStmt);
        } while (current.isJust());
        return false;
    }

    @NotNull
    @Override
    public ValidationContext reduceBindingIdentifier(@NotNull BindingIdentifier node) {
        ValidationContext s = super.reduceBindingIdentifier(node);
        if (!checkIsValidIdentifierName(node.name)) {
            if (node.name.equals("*default*")) {
                s.addBindingIdentifierCalledDefault(node);
            } else {
                s.addError(new ValidationError(node, ValidationErrorMessages.VALID_BINDING_IDENTIFIER_NAME));
            }
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceBreakStatement(@NotNull BreakStatement node) {
        ValidationContext s = super.reduceBreakStatement(node);
        if (node.label.isJust() && !checkIsValidIdentifierName(node.label.just())) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_BREAK_STATEMENT_LABEL));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceCatchClause(@NotNull CatchClause node, @NotNull ValidationContext binding, @NotNull ValidationContext body) {
        ValidationContext s = super.reduceCatchClause(node, binding, body);
        if (node.binding instanceof MemberExpression) {
            s.addError(new ValidationError(node, ValidationErrorMessages.CATCH_CLAUSE_BINDING_NOT_MEMBER_EXPRESSION));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceContinueStatement(@NotNull ContinueStatement node) {
        ValidationContext s = super.reduceContinueStatement(node);
        if (node.label.isJust() && !checkIsValidIdentifierName(node.label.just())) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_CONTINUE_STATEMENT_LABEL));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceDirective(@NotNull Directive node) {
        ValidationContext s = super.reduceDirective(node);
        if (!checkIsStringLiteral(node.rawValue)) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_DIRECTIVE));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceExportDefault(@NotNull ExportDefault node, @NotNull ValidationContext body) {
        ValidationContext s = super.reduceExportDefault(node, body);
        if (node.body instanceof FunctionDeclaration || node.body instanceof ClassDeclaration) {
            s.clearBindingIdentifiersCalledDefault();
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceExportSpecifier(@NotNull ExportSpecifier node) {
        ValidationContext s = super.reduceExportSpecifier(node);
        if (node.name.isJust() && !checkIsValidIdentifierName(node.name.just())) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_EXPORT_SPECIFIER_NAME));
        }
        if (!checkIsValidIdentifierName(node.exportedName)) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_EXPORTED_NAME));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceForInStatement(@NotNull ForInStatement node, @NotNull ValidationContext left, @NotNull ValidationContext right, @NotNull ValidationContext body) {
        ValidationContext s = super.reduceForInStatement(node, left, right, body);
        if (node.left instanceof VariableDeclaration) {
            VariableDeclaration varDec = (VariableDeclaration) node.left;
            if (varDec.declarators.length != 1) {
                s.addError(new ValidationError(node, ValidationErrorMessages.ONE_VARIABLE_DECLARATOR_IN_FOR_IN));
            }
            if (varDec.declarators.maybeHead().just().init.isJust()) {
                s.addError(new ValidationError(node, ValidationErrorMessages.NO_INIT_IN_VARIABLE_DECLARATOR_IN_FOR_IN));
            }
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceForOfStatement(@NotNull ForOfStatement node, @NotNull ValidationContext left, @NotNull ValidationContext right, @NotNull ValidationContext body) {
        ValidationContext s = super.reduceForOfStatement(node, left, right, body);
        if (node.left instanceof VariableDeclaration) {
            VariableDeclaration varDec = (VariableDeclaration) node.left;
            if (varDec.declarators.length != 1) {
                s.addError(new ValidationError(node, ValidationErrorMessages.ONE_VARIABLE_DECLARATOR_IN_FOR_OF));
            }
            if (varDec.declarators.maybeHead().just().init.isJust()) {
                s.addError(new ValidationError(node, ValidationErrorMessages.NO_INIT_IN_VARIABLE_DECLARATOR_IN_FOR_OF));
            }
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceFormalParameters(@NotNull FormalParameters node, @NotNull ImmutableList items, @NotNull Maybe rest) {
        ValidationContext s = super.reduceFormalParameters(node, items, rest);
        node.items.foreach(x -> {
            if (x instanceof Binding) {
                if (x instanceof MemberExpression) {
                    s.addError(new ValidationError(node, ValidationErrorMessages.FORMAL_PARAMETER_ITEMS_NOT_MEMBER_EXPRESSION));
                }
            } else if (x instanceof BindingWithDefault) {
                if (((BindingWithDefault) x).binding instanceof MemberExpression) {
                    s.addError(new ValidationError(node, ValidationErrorMessages.FORMAL_PARAMETER_ITEMS_BINDING_NOT_MEMBER_EXPRESSION));
                }
            }
        });
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceFunctionBody(@NotNull FunctionBody node, @NotNull ImmutableList directives, @NotNull ImmutableList statements
    ) {
        ValidationContext s = super.reduceFunctionBody(node, directives, statements);
        s.clearFreeReturnStatements();
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceFunctionDeclaration(@NotNull FunctionDeclaration node, @NotNull ValidationContext name, @NotNull ValidationContext params, @NotNull ValidationContext body) {
        ValidationContext s = super.reduceFunctionDeclaration(node, name, params, body);
        if (node.isGenerator) {
            s.clearYieldExpressionsNotInGeneratorContext();
            s.clearYieldGeneratorExpressionsNotInGeneratorContext();
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceFunctionExpression(@NotNull FunctionExpression node, @NotNull Maybe name, @NotNull ValidationContext params, @NotNull ValidationContext body) {
        ValidationContext s = super.reduceFunctionExpression(node, name, params, body);
        if (node.isGenerator) {
            s.clearYieldExpressionsNotInGeneratorContext();
            s.clearYieldGeneratorExpressionsNotInGeneratorContext();
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceIdentifierExpression(@NotNull IdentifierExpression node) {
        ValidationContext s = super.reduceIdentifierExpression(node);
        if (!checkIsValidIdentifierName(node.name)) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_IDENTIFIER_NAME));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceIfStatement(@NotNull IfStatement node, @NotNull ValidationContext test, @NotNull ValidationContext consequent, @NotNull Maybe alternate) {
        ValidationContext s = super.reduceIfStatement(node, test, consequent, alternate);
        if (isProblematicIfStatement(node)) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_IF_STATEMENT));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceImportSpecifier(@NotNull ImportSpecifier node, @NotNull ValidationContext binding) {
        ValidationContext s = super.reduceImportSpecifier(node, binding);
        if (node.name.isJust() && !checkIsValidIdentifierName(node.name.just())) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_IMPORT_SPECIFIER_NAME));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceLabeledStatement(@NotNull LabeledStatement node, @NotNull ValidationContext body) {
        ValidationContext s = super.reduceLabeledStatement(node, body);
        if (!checkIsValidIdentifierName(node.label)) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_LABEL));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceLiteralNumericExpression(@NotNull LiteralNumericExpression node) {
        ValidationContext s = super.reduceLiteralNumericExpression(node);
        if (node.value.isNaN()) {
            s.addError(new ValidationError(node, ValidationErrorMessages.LITERAL_NUMERIC_VALUE_NOT_NAN));
        }
        if (node.value < 0) {
            s.addError(new ValidationError(node, ValidationErrorMessages.LITERAL_NUMERIC_VALUE_NOT_NEGATIVE));
        }
        if (node.value.isInfinite()) {
            s.addError(new ValidationError(node, ValidationErrorMessages.LITERAL_NUMERIC_VALUE_NOT_INFINITE));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceLiteralRegExpExpression(@NotNull LiteralRegExpExpression node) {
        ValidationContext s = super.reduceLiteralRegExpExpression(node);
        if (!checkIsLiteralRegExpPattern(node.pattern)) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_REG_EX_PATTERN));
        }
        if (node.flags.length() > 0) {
            boolean hasValidFlags = node.flags.chars().allMatch(x -> x == 'g' || x == 'i' || x == 'm' || x == 'u' || x == 'y');
            if (!hasValidFlags) {
                s.addError(new ValidationError(node, ValidationErrorMessages.VALID_REG_EX_FLAG));
            }
        }
        Map charMap = new HashMap<>();
        node.flags.chars().forEach(x -> {
            if (charMap.containsKey(x)) {
                s.addError(new ValidationError(node, ValidationErrorMessages.NO_DUPLICATE_REG_EX_FLAG));
            } else {
                charMap.put(x, true);
            }
        });
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceMethod(@NotNull Method node, @NotNull ValidationContext params, @NotNull ValidationContext body, @NotNull ValidationContext name) {
        ValidationContext s = super.reduceMethod(node, params, body, name);
        if (node.isGenerator) {
            s.clearYieldExpressionsNotInGeneratorContext();
            s.clearYieldGeneratorExpressionsNotInGeneratorContext();
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceModule(@NotNull Module node, @NotNull ImmutableList directives, @NotNull ImmutableList items
    ) {
        ValidationContext s = super.reduceModule(node, directives, items);
        s.enforceFreeReturnStatements(returnStatement -> new ValidationError(returnStatement, ValidationErrorMessages.RETURN_STATEMENT_IN_FUNCTION_BODY));
        s.enforceBindingIdentifiersCalledDefault(bindingIdentifier -> new ValidationError(bindingIdentifier, ValidationErrorMessages.BINDING_IDENTIFIERS_CALLED_DEFAULT));
        s.enforceYieldExpressionsNotInGeneratorContext(yieldExpression -> new ValidationError(yieldExpression, ValidationErrorMessages.VALID_YIELD_EXPRESSION_POSITION));
        s.enforceYieldGeneratorExpressionsNotInGeneratorContext(yieldGeneratorExpression -> new ValidationError(yieldGeneratorExpression, ValidationErrorMessages.VALID_YIELD_GENERATOR_EXPRESSION_POSITION));
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceReturnStatement(@NotNull ReturnStatement node, @NotNull Maybe expression
    ) {
        ValidationContext s = super.reduceReturnStatement(node, expression);
        s.addFreeReturnStatement(node);
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceScript(@NotNull Script node, @NotNull ImmutableList directives, @NotNull ImmutableList statements
    ) {
        ValidationContext s = super.reduceScript(node, directives, statements);
        s.enforceFreeReturnStatements(returnStatement -> new ValidationError(returnStatement, ValidationErrorMessages.RETURN_STATEMENT_IN_FUNCTION_BODY));
        s.enforceBindingIdentifiersCalledDefault(bindingIdentifier -> new ValidationError(bindingIdentifier, ValidationErrorMessages.BINDING_IDENTIFIERS_CALLED_DEFAULT));
        s.enforceYieldExpressionsNotInGeneratorContext(yieldExpression -> new ValidationError(yieldExpression, ValidationErrorMessages.VALID_YIELD_EXPRESSION_POSITION));
        s.enforceYieldGeneratorExpressionsNotInGeneratorContext(yieldGeneratorExpression -> new ValidationError(yieldGeneratorExpression, ValidationErrorMessages.VALID_YIELD_GENERATOR_EXPRESSION_POSITION));
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceSetter(@NotNull Setter node, @NotNull ValidationContext name, @NotNull ValidationContext parameter, @NotNull ValidationContext body) {
        ValidationContext s = super.reduceSetter(node, name, parameter, body);
        if (node.param instanceof Binding) {
            if (node.param instanceof MemberExpression) {
                s.addError(new ValidationError(node, ValidationErrorMessages.SETTER_PARAM_NOT_MEMBER_EXPRESSION));
            }
        } else if (node.param instanceof BindingWithDefault) {
            if (((BindingWithDefault) node.param).binding instanceof MemberExpression) {
                s.addError(new ValidationError(node, ValidationErrorMessages.SETTER_PARAM_BINDING_NOT_MEMBER_EXPRESSION));
            }
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceShorthandProperty(@NotNull ShorthandProperty node) {
        ValidationContext s = super.reduceShorthandProperty(node);
        if (!checkIsValidIdentifierName(node.name)) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_SHORTHAND_PROPERTY_NAME));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceStaticMemberExpression(@NotNull StaticMemberExpression node, @NotNull ValidationContext object) {
        ValidationContext s = super.reduceStaticMemberExpression(node, object);
        if (!checkIsValidIdentifierName(node.property)) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_STATIC_MEMBER_EXPRESSION_PROPERTY_NAME));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceTemplateElement(@NotNull TemplateElement node) {
        ValidationContext s = super.reduceTemplateElement(node);
        if (!checkIsStringLiteral(node.rawValue)) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VALID_TEMPLATE_ELEMENT_VALUE));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceTemplateExpression(@NotNull TemplateExpression node, @NotNull Maybe tag, @NotNull ImmutableList elements) {
        ValidationContext s = super.reduceTemplateExpression(node, tag, elements);
        if (elements.length > 0) {
            if (node.elements.length % 2 == 0) {
                s.addError(new ValidationError(node, ValidationErrorMessages.ALTERNATING_TEMPLATE_EXPRESSION_ELEMENTS));
            } else {
                node.elements.mapWithIndex((i, x) -> {
                    if (i % 2 == 0) {
                        if (!(x instanceof TemplateElement)) {
                            s.addError(new ValidationError(node, ValidationErrorMessages.ALTERNATING_TEMPLATE_EXPRESSION_ELEMENTS));
                        }
                    } else {
                        if (!(x instanceof Expression)) {
                            s.addError(new ValidationError(node, ValidationErrorMessages.ALTERNATING_TEMPLATE_EXPRESSION_ELEMENTS));
                        }
                    }
                    return true;
                });
            }
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceVariableDeclaration(@NotNull VariableDeclaration node, @NotNull ImmutableList declarators) {
        ValidationContext s = super.reduceVariableDeclaration(node, declarators);
        if (node.declarators.length == 0) {
            s.addError(new ValidationError(node, ValidationErrorMessages.NOT_EMPTY_VARIABLE_DECLARATORS_LIST));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceVariableDeclarationStatement(@NotNull VariableDeclarationStatement node, @NotNull ValidationContext declaration) {
        ValidationContext s = super.reduceVariableDeclarationStatement(node, declaration);
        if (node.declaration.kind.equals(VariableDeclarationKind.Const)) {
            node.declaration.declarators.foreach(x -> {
                if (x.getInit().isNothing()) {
                    s.addError(new ValidationError(node, ValidationErrorMessages.CONST_VARIABLE_DECLARATION_MUST_HAVE_INIT));
                }
            });
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceVariableDeclarator(@NotNull VariableDeclarator node, @NotNull ValidationContext binding, @NotNull Maybe init) {
        ValidationContext s = super.reduceVariableDeclarator(node, binding, init);
        if (node.binding instanceof MemberExpression) {
            s.addError(new ValidationError(node, ValidationErrorMessages.VARIABLE_DECLARATION_BINDING_NOT_MEMBER_EXPRESSION));
        }
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceYieldExpression(@NotNull YieldExpression node, @NotNull Maybe expression) {
        ValidationContext s = super.reduceYieldExpression(node, expression);
        s.addYieldExpressionsNotInGeneratorContext(node);
        return s;
    }

    @NotNull
    @Override
    public ValidationContext reduceYieldGeneratorExpression(@NotNull YieldGeneratorExpression node, @NotNull ValidationContext expression) {
        ValidationContext s = super.reduceYieldGeneratorExpression(node, expression);
        s.addYieldGeneratorExpressionsNotInGeneratorContext(node);
        return s;
    }

    @NotNull
    private Maybe trailingStatement(Statement node) {
        if (node instanceof IfStatement) {
            return Maybe.just(((IfStatement) node).alternate.orJust(((IfStatement) node).consequent));
        } else if (node instanceof LabeledStatement) {
            return Maybe.just(((LabeledStatement) node).body);
        } else if (node instanceof IterationStatement) {
            return Maybe.just(((IterationStatement) node).body);
        }
        return Maybe.nothing();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy