Skip to content

Commit

Permalink
[GR-60495] Adds utility for checking import order in espresso.
Browse files Browse the repository at this point in the history
PullRequest: graal/19575
  • Loading branch information
rakachan committed Dec 18, 2024
2 parents 827906d + 9a2c6ef commit d6ddf70
Show file tree
Hide file tree
Showing 138 changed files with 608 additions and 371 deletions.
2 changes: 1 addition & 1 deletion espresso/ci/ci_common/common.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ local benchmark_suites = ['dacapo', 'renaissance', 'scala-dacapo'];

local _builds = [
// Gates
that.jdk21_gate_linux_amd64 + that.eclipse + that.jdt + that.predicates(false, false, false) + that.espresso_gate(allow_warnings=false, tags='style,fullbuild', timelimit='35:00', name='gate-espresso-style-jdk21-linux-amd64'),
that.jdk21_gate_linux_amd64 + that.eclipse + that.jdt + that.predicates(false, false, false) + that.espresso_gate(allow_warnings=false, tags='style,fullbuild,imports', timelimit='35:00', name='gate-espresso-style-jdk21-linux-amd64'),
],

builds: utils.add_defined_in(_builds, std.thisFile),
Expand Down
190 changes: 190 additions & 0 deletions espresso/mx.espresso/import_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#

"""Provides import ordering capabilities for .java files."""

from glob import iglob

# If a given line is an import, it will end with this suffix.
# Used to strip this suffix for faster string comparison.
SUFFIX = ";\n"

STATIC_PREFIX = "import static "
REGULAR_PREFIX = "import "

def verify_order(path, prefix_order):
"""
Verifies import order of java files under the given path.
Iterates over all '.java' files in the given path, recursively over its subfolders.
It then checks that imports in these files are ordered.
Here are the rules:
1. All imports that starts with a suffix that appears in this list must
be found before any other import with a suffix that appears later in
this list.
2. All imports with a given suffix must be in lexicographic order within
all other imports with the same prefix.
3. Static imports must appear before regular imports.
:param path: Where to look for the java files.
:param prefix_order: An ordered list of expected suffixes.
:return: The list of files violating the specified order.
"""

# Validate the prefixes
err = validate_format(prefix_order)
if err:
# Failure is represented with a non-empty list
return [err]

# Start building definitive list of import prefixes
static_prefixes = []
regular_prefixes = []

for prefix in prefix_order:
if prefix:
# If prefix is "abc", add "import abc"
regular_prefixes.append(REGULAR_PREFIX + prefix + '.')
# Eclipse formatting does not enforce prefix order for static imports.
else:
# Empty prefix means everything will match.
# Empty prefix is added manually below.
break

# Ensure we have the empty prefix
# Add "import static "
static_prefixes.append(STATIC_PREFIX)
# Add "import "
regular_prefixes.append(REGULAR_PREFIX)

# Ensures static imports are before regular imports.
prefix_format = static_prefixes + regular_prefixes

invalid_files = []

def is_sorted(li):
if len(li) <= 1:
return True
return all(li[i] <= li[i + 1] for i in range(len(li) - 1))

def check_file(to_check, prefix_format):
imports, prefix_ordered = get_imports(to_check, prefix_format)

if not prefix_ordered:
return False

for import_list in imports:
if not is_sorted(import_list):
return False

return True

for file in iglob(path + '/**/*.java', recursive=True):
if not check_file(file, prefix_format):
invalid_files.append(file)

return invalid_files

def validate_format(prefix_order):
"""
Validates a given ordered list of prefix for import order verification.
Returns the reason for failure of validation if any, or an empty string
if the prefixes are well-formed.
"""
for prefix in prefix_order:
if prefix.endswith('.'):
return "Invalid format for the ordered prefixes: \n'" + prefix + "' must not end with a '.'"
return ""

def get_imports(file, prefix_format):
"""
Obtains list of imports list, each corresponding to each specified prefix.
Also returns whether the found prefixes were ordered.
In case the prefixes where not ordered, the last element of the returned list will contain
every import after the violating line
"""
def add_import(li, value, prefix, suf):
to_add = value[len(prefix):]
if to_add.endswith(suf):
to_add = to_add[:len(to_add) - len(suf)]
li.append(to_add)

def enter_fail_state(imports, prefix_format, cur_prefix_imports):
if cur_prefix_imports:
imports.append(cur_prefix_imports)
return False, len(prefix_format), ""

with open(file) as f:
imports = []
prefix_ordered = True

cur_prefix_idx = 0
cur_prefix = prefix_format[cur_prefix_idx]

cur_prefix_imports = []

for line in f.readlines():
ignore = not line.startswith("import")
if ignore:
# start of class declaration, we can stop looking for imports.
end = 'class ' in line or 'interface ' in line or 'enum ' in line or 'record ' in line
if end:
break
continue

if line.startswith(cur_prefix):
# If we are still ensuring prefix ordering, ensure that this line does not belong
# to a previous prefix.
if prefix_ordered:
for i in range(cur_prefix_idx):
if line.startswith(prefix_format[i]):
# A match for a previous prefix was found: enter fail state
prefix_ordered, cur_prefix_idx, cur_prefix = enter_fail_state(imports, prefix_format, cur_prefix_imports)
cur_prefix_imports = []
add_import(cur_prefix_imports, line, cur_prefix, SUFFIX)
else:
# cur_prefix not found, advance to next prefix if found, report failure if not.
for i in range(cur_prefix_idx + 1, len(prefix_format)):
if line.startswith(prefix_format[i]):
# Report imports for current prefix,
if cur_prefix_imports:
imports.append(cur_prefix_imports)
# Set state to next prefix.
cur_prefix = prefix_format[i]
cur_prefix_idx = i
cur_prefix_imports = []
add_import(cur_prefix_imports, line, cur_prefix, SUFFIX)
break
else:
# On failure, dump remaining lines into the last cur_prefix_imports.
prefix_ordered, cur_prefix_idx, cur_prefix = enter_fail_state(imports, prefix_format, cur_prefix_imports)
cur_prefix_imports = []
add_import(cur_prefix_imports, line, cur_prefix, SUFFIX)

if cur_prefix_imports:
imports.append(cur_prefix_imports)

return imports, prefix_ordered
47 changes: 46 additions & 1 deletion espresso/mx.espresso/mx_espresso.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@
from mx_gate import Task, add_gate_runner
from mx_jackpot import jackpot
from os.path import join, isabs, exists, dirname, relpath, basename
from import_order import verify_order, validate_format

_suite = mx.suite('espresso')

# re-export custom mx project classes, so they can be used from suite.py
from mx_sdk_shaded import ShadedLibraryProject # pylint: disable=unused-import
from mx_sdk_shaded import ShadedLibraryProject

# JDK compiled with the Sulong toolchain.
espresso_llvm_java_home = mx.get_env('ESPRESSO_LLVM_JAVA_HOME') or mx.get_env('LLVM_JAVA_HOME')
Expand Down Expand Up @@ -134,9 +135,48 @@ def _run_espresso_meta(args, nonZeroIsFatal=True, timeout=None):
] + _espresso_standalone_command(args, allow_jacoco=False), nonZeroIsFatal=nonZeroIsFatal, timeout=timeout)


def _run_verify_imports(s):
# Look for the format specification in the suite
prefs = s.eclipse_settings_sources().get('org.eclipse.jdt.ui.prefs')
prefix_order = []
if prefs:
for pref in prefs:
with open(pref) as f:
for line in f.readlines():
if line.startswith('org.eclipse.jdt.ui.importorder'):
key_value_sep_index = line.find('=')
if key_value_sep_index != -1:
value = line.strip()[key_value_sep_index + 1:]
prefix_order = value.split(';')

# Validate import order format
err = validate_format(prefix_order)
if err:
mx.abort(err)

# Find invalid files
invalid_files = []
for project in s.projects:
if isinstance(project, ShadedLibraryProject):
# Ignore shaded libraries
continue
for src_dir in project.source_dirs():
invalid_files += verify_order(src_dir, prefix_order)

if invalid_files:
mx.abort("The following files have wrong imports order:\n" + '\n'.join(invalid_files))

print("All imports correctly ordered!")

def _run_verify_imports_espresso(args):
if args:
mx.abort("No arguments expected for verify-imports")
_run_verify_imports(_suite)

class EspressoTags:
jackpot = 'jackpot'
verify = 'verify'
imports = 'imports'


def _espresso_gate_runner(args, tasks):
Expand All @@ -149,6 +189,10 @@ def _espresso_gate_runner(args, tasks):
if t:
mx_sdk_vm.verify_graalvm_configs(suites=['espresso'])

with Task('Espresso: verify import order', tasks, tags=[EspressoTags.imports]) as t:
if t:
_run_verify_imports(_suite)

mokapot_header_gate_name = 'Verify consistency of mokapot headers'
with Task(mokapot_header_gate_name, tasks, tags=[EspressoTags.verify]) as t:
if t:
Expand Down Expand Up @@ -831,6 +875,7 @@ def gen_gc_option_check(args):
'java-truffle': [_run_java_truffle, '[args]'],
'espresso-meta': [_run_espresso_meta, '[args]'],
'gen-gc-option-check': [gen_gc_option_check, '[path to isolate-creation-only-options.txt]'],
'verify-imports': [_run_verify_imports_espresso, ''],
})


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
*/
package com.oracle.truffle.espresso.classfile;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;

import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;

/**
* Operations for sequentially scanning data items in a class file. Any IO exceptions that occur
* during scanning are converted to {@link ParserException.ClassFormatError}s.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
*/
package com.oracle.truffle.espresso.classfile;

import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;

import java.io.File;

import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;

/**
* An entry in a classpath is a file system path that denotes an existing directory, an existing
* zip/jar file or a file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
*/
package com.oracle.truffle.espresso.classfile;

import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;

import java.io.File;

import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;

/**
* Encapsulates the contents of a file loaded from an {@linkplain ClasspathEntry entry} on a
* classpath.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,23 @@
*/
package com.oracle.truffle.espresso.classfile;

import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.CLASS;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.DOUBLE;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FIELD_REF;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FLOAT;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTEGER;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTERFACE_METHOD_REF;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INVOKEDYNAMIC;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.LONG;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.METHOD_REF;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.NAME_AND_TYPE;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.STRING;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.UTF8;

import java.util.Arrays;
import java.util.Formatter;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.espresso.classfile.descriptors.Symbol;
import com.oracle.truffle.espresso.classfile.descriptors.Symbol.ModifiedUTF8;
import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant;
import com.oracle.truffle.espresso.classfile.constantpool.ClassMethodRefConstant;
import com.oracle.truffle.espresso.classfile.constantpool.DoubleConstant;
Expand All @@ -40,22 +54,8 @@
import com.oracle.truffle.espresso.classfile.constantpool.PoolConstant;
import com.oracle.truffle.espresso.classfile.constantpool.StringConstant;
import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant;

import java.util.Arrays;
import java.util.Formatter;

import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.CLASS;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.DOUBLE;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FIELD_REF;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FLOAT;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTEGER;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTERFACE_METHOD_REF;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INVOKEDYNAMIC;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.LONG;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.METHOD_REF;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.NAME_AND_TYPE;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.STRING;
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.UTF8;
import com.oracle.truffle.espresso.classfile.descriptors.Symbol;
import com.oracle.truffle.espresso.classfile.descriptors.Symbol.ModifiedUTF8;

/**
* Immutable, shareable constant-pool representation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@
*/
package com.oracle.truffle.espresso.classfile;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;

import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.espresso.classfile.descriptors.Symbol;
import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant;
import com.oracle.truffle.espresso.classfile.constantpool.PoolConstant;
import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
import com.oracle.truffle.espresso.classfile.descriptors.Symbol;

/**
* Immutable constant pool implementation backed by an array of constants.
Expand Down
Loading

0 comments on commit d6ddf70

Please sign in to comment.