ブログ

JDK

Java 15の新機能(6)-Foreign-Memoryアクセス

Javaヒープ外にメモリを割り当てたり,読み込んだりする手段が提供された。MemorySegmentインタフェースを用いて,メモリセグメントを取得する。asByteBuffer()メソッドを使えば,ByteBufferのメソッドを使って操作できるため,これまでと同じように扱うことができる。以下のプログラムでは4バイトのメモリを割り当て,全バイトに0xFFを書き込んでいる。そして,その領域をint型で読み込んでintでの値を出力する。

import java.nio.ByteBuffer;
import jdk.incubator.foreign.MemorySegment;

public class ForeignMemoryExample
{
	public static void main(final String[] args) throws Exception
	{
		try(MemorySegment segment = MemorySegment.allocateNative(4L))
		{
			ByteBuffer buffer = segment.asByteBuffer();
			buffer.put(new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF});
			int intValue = buffer.getInt(0);
			System.out.println(String.format("int型での値は:%d", intValue));
		}
	}
}

これをコンパイルする。その際,module-info.javaを作成して,jdk.incubator.foreignモジュールを利用できるように記述しておく必要がある。

実行するには,次のコマンドを実行する。

java –add-modules jdk.incubator.foreign –enable-preview ForeignMemoryExample

実行すると,次のように出力されて,2の補数表現でintが取得された。

int型での値は:-1

JDK

Java 15の新機能(5)-Record型

Record型はJava 14にプレビュー機能として取り入れられて,引き続きプレビューとなっている。新たにシールインタフェースを実装できるように拡張されている。次のようにAnimalインタフェースを実装するレコードPersonを定義できる。

public class RecordExample
{
    public static void main(String[] args)
    {
        Person person = new Person("はらだ たかひこ", 38);
        System.out.printf("名前: %s\n", person.getName());
        System.out.printf("年齢: %d歳\n", person.getAge());
    }
}
sealed interface Animal permits Person
{
    public String getName();
}
record Person(String name, int age) implements Animal
{
    @Override
    public String getName()
    {
        return name;
    }
    public int getAge()
    {
        return age;
    }
}

レコード型については,次の記事を参照していただきたい。

https://jy-info-sys.com/2020/03/23/java-14-%e6%96%b0%e6%a9%9f%e8%83%bd-4/

JDK

Java 15の新機能(3)-テキストブロック

Java 14でプレビューとなっていたテキストブロック機能が正式リリースとなった。ヒアドキュメントのように複数行にわたって文字列リテラルを記述できる機能である。次のように「”」3つでリテラルを囲むだけでよい。

public class TextBlock
{
    private static final String text = """
            <html>
                <head>
                </head>
                <body>
                </body>
            </html>""";
    public static void main(String[] args)
    {
        System.out.println(text);
    }
}

上のコードをコンパイルして実行すると,以下のように出力される。

<html>
    <head>
    </head>
    <body>
    </body>
</html>

JDK

Java 15の新機能(2)ーHiddenクラス

Hiddenクラスは、かなり特殊な用途でしか使われないと思われる。Javaバイトコードが直接アクセスできないクラスを動的に作り出すことを可能にする機能である。フレームワークなどが動的に作り出すクラスが、リフレクションによって検出されてしまうのを避ける意図があるという。

 このクラスはその特性ゆえに、実験するには少々手間がかかる。java. lang. invoke. MethodHandles. Lookup # defineHiddenClass()メソッドにbyte[]型でクラス定義を与えること以外にはインスタンスを作成する方法がないようだ。そこで、下準備として、次のようなHello Worldプログラムを作成して、コンパイルしclassファイルを用意しておく。このclassファイルを読み込んで利用することにする。

public class Hello
{
    public final String message = "Hello World!";
    public static void main(String[] args)
    {
        Hello instance = new Hello();
        instance.greet();
    }
    public void greet()
    {
        System.out.println(message);
    }
}

続いて、これをコンパイルしたバイトコードを使って、Hiddenクラスのインスタンスを使用するプログラムを作成する。FilesクラスのstaticメソッドreadAllBytes()メソッドを使って、上のHelloクラスをコンパイルしたHello.classファイルをbyte[]型の変数bytesに読み込む。続いて、MethodHandles.lookup()によって、ルックアップオブジェクトlookupを取得する。lookupのdefineHiddenClass()メソッドに、Helloクラスのバイトコードを読み込んだ配列bytesを渡して、Hiddenクラスを定義する。

lookupのfindClass()メソッドを使って、Class<?>オブジェクトを取得する。このオブジェクトを取得できれば、あとは従来のようなリフレクションを使ってインスタンスを作成したり、メソッドを呼び出したりできる。

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.*;
import java.io.*;
import java.nio.file.*;

public class HiddenExample
{
    public static void main(final String[] args) throws Exception
    {
        byte[] bytes = Files.readAllBytes(Paths.get("Hello.class"));
        Lookup lookup = MethodHandles.lookup();
        lookup.defineHiddenClass(bytes, false);

        Class<?> clazz = lookup.findClass("Hello");

        Constructor constuctor = clazz.getDeclaredConstructor();
        Object hiddenClassInstance = constuctor.newInstance();
        
        Method method = clazz.getDeclaredMethod("greet");
        method.invoke(hiddenClassInstance);
        try
        {
            Field field = clazz.getField("message");
            field.set(hiddenClassInstance, "Goodbye!");
        }
        catch(IllegalAccessException e)
        {
            System.out.println("    finalフィールドは変更できません "+ e.getMessage());
        }
    }
}

このプログラムをコンパイルして実行すると、次のような出力が得られる。Hello World!と出力されていて、Helloクラスのgreet()メソッドが実行されていることがわかる。また、finalフィールドの値を変更しようとして、例外も発生している。

Hello World!
    finalフィールドは変更できません Can not set final java.lang.String field Hello.message to java.lang.String

普通にクラスを定義して、それを利用することを考えると、面倒なことこの上ない。それでも、フレームワークを作っている人には役に立つのであろう。

JDK

Java 15の新機能(1)ー「シールクラス」

Java 15で導入された新機能のうち、最初に紹介するのはシールクラス(Sealed class)である。まだプレビューとなっていて、正式な仕様ではない。シールクラスは、自身を継承できるクラスを指定したクラスだけに制限することができる。

次のようにVehicleクラスを定義する。sealedがシールクラスを示している。そして、permitsに続くBusとTruckが継承を許されたクラスである。すなわち、VehicleのサブクラスはBusとTruckのみが存在できる。

public sealed class Vehicle permits Bus, Truck
{
    protected int max_speed;
    @Override
    public String toString()
    {
        return this.getClass().getName();
    }
}

それでは、シールクラスを継承したBusクラスとTruckクラスを作成してみる。とりあえず、中身は空にして以下のようにBusクラスを定義してコンパイルしてみよう。プレビュー機能であるので、コンパイルオプションをつけてコンパイラを実行する。

javac –enable-preview -source 15 -Xlint:preview *.java

class Bus extends Vehicle
{
}
class Truck extends Vehicle
{
}

Vehicle.java:10: エラー: sealed、non-sealedまたはfinal修飾子が必要です
class Bus extends Vehicle
^
Vehicle.java:13: エラー: sealed、non-sealedまたはfinal修飾子が必要です
class Truck extends Vehicle
^
エラー2個

 コンパイルすると、上のようなコンパイルエラーが起きた。そこで、BusクラスとTruckクラスとfinalにして、再度コンパイルする。以下のようにするとコンパイルが成功した。

public sealed class Vehicle permits Bus, Truck
{
    protected int max_speed;
    @Override
    public String toString()
    {
        return this.getClass().getName();
    }
}
final class Bus extends Vehicle
{
}
final class Truck extends Vehicle
{
}

Vehicleクラスを継承して、新たにDogというクラスを定義してみる。

final class Dog extends Vehicle
{
}

コンパイルしようとすると、次のようなコンパイルエラーが発生して、Vehicleを継承できないことがわかる。

Vehicle.java:16: エラー: クラスはシール・クラスVehicleを拡張できません
final class Dog extends Vehicle
      ^
エラー1個

雑記

API Hell

最近、”API Hell”(API地獄)と言えるような状況が散見されるようになってきた。かつて、Dll Hellと呼ばれたものと似たような現象が、Web-APIで起っているのである。

 dllは、プログラムの部品をdllとして動的にリンクすることで、部品の共通化、再利用や、アプリケーションの更新を容易にするなどを実現した。しかし、次第にアプリケーションを動かすためのdllの管理が煩雑になり、バージョンが合わない等で動作しなくなるといった問題が起こるようになった。これがDll Hellである。

 近年Web-APIによって、多くのシステムを連携させることが増えてきた。これにより、他のシステムと連携させて、より便利なシステムを構築できるようになった。インタフェースさえきちんと定義できれば、企業間のシステム連携も容易にできる。しかし、APIの裏側での動作が完全にブラックボックスになってしまい、エラーが起きた際の調査が困難であったり、APIが廃止されてしまった結果、そのAPIを利用していたシステムが停止してしまったりといった問題が発生している。Dll Hellの教訓から、バージョンアップにはある程度配慮して設計がなされるが、Web-APIならではの問題も多い。

情報セキュリティ

マネジメントシステム認証を過信すべからず

情報セキュリティマネジメントシステム(ISMS)やプライバシーマークなどの認証を取得して,「うちはしっかりやっています」と宣伝する企業は多数あるが,これらの認証を過信してはならない。認証は2年あるいは3年ごとに継続審査があり,その時の状況でほぼ決まっていると言ってもよい状況である。

 現地審査の時間はまったく足りない。2日程度であることが多いと聞く。審査員も数名しか来ない。この程度では,現地で担当者の説明を聞いたら終わってしまう。

 加えて文書の捏造が横行している。実際には月次でしっかりチェックするというルールを設けていても,その通りに運用できずにチェックシートを過去の日付で作成してしまうような行為が行われているのである。このような行為は認証の信頼性を損なうので,厳に慎むべきである。

 認証を全否定するものではないが,「認証を取得している会社だから大丈夫」とはならないことに留意が必要だ。

情報セキュリティ

立会人型の電子契約サービス

9月4日に総務省,経済産業省,法務省の連名により,「利用者の指示に基づきサービス提供事業者自身の署名鍵により暗号化等を行う電子契約サービスに関するQ&A(電子署名法3条に関するQ&A)」という文書が出された。この中で,「立会人型の電子契約サービス」については,「技術的・機能的に見て、サービス提供事業者の意思が介在する余地がなく、利用者の意思のみに基づいて機械的に暗号化されたものであることが担保されているものであり、かつサービス提供事業者が電子文書に行った措置について付随情報を含めて全体を1つの措置と捉え直すことによって、当該措置が利用者の意思に基づいていることが明らかになる場合には、同法第2条第1項に規定する電子署名に該当すると考えられる」と述べて,有効性を認める見解を示している。

 問題となるのが,「サービス提供事業者の意思が介在する余地がなく」という点であろう。果たして,そのような仕組みが現実的に可能なのか,甚だ疑問である。サービス提供事業者は,その意に反するような電子文書の作成を許容するであろうか? 例えば,お金を払わない利用者のアカウントを停止するようなことは,通常の事業者であれば容易に想像がつく。ある文書には電子署名をするけれども,別の文書には電子署名をしないということは非常に簡単であるし,逆に言うと,それができないようにすることは極めて難しい。

 「十分な水準の固有性を満たしていると認められるためには、①利用者とサービス提供事業者の間で行われるプロセス及び②①における利用者の行為を受けてサービス提供事業者内部で行われるプロセスのいずれにおいても十分な水準の固有性が満たされている必要があると考えられる」という部分の①について,「利用者」が1者のみではない(実際にはそのようなケースの方が多いと考えられる)場合が問題となる。AとBとがこのようなサービスを用いて電子契約をする場合,Aがサービス提供事業者とサービスの提供を受ける契約を結び利用料を支払うとする。AとBとが電子契約を締結する場合,サービス提供事業者とBとの間には契約関係がないか,あっても,無償の契約である。この場合,サービス提供事業者の提供するサービスにはAとBとの間で違いが生じるであろう。これは公平性という点で極めて問題であると言える。サービス提供事業者は,利用料を支払っている利用者に便宜を図ると考えるのが自然である。

 第3の問題として,「利用者が2要素による認証を受けなければ措置を行うことができない仕組みが備わっているような場合には、十分な水準の固有性が満たされていると認められ得ると考えられる」という部分について,MFA(多要素認証)を過剰評価していないであろうか。特に組織内においては,共有のメールアドレスを使用する,情報システムのパスワードを使いまわす,多要素認証のトークンを共有している,などのケースは多いと思われる。個人間の認証においてはMFAはある程度有効と考えられるが,組織においては使いまわしが横行している現状を考えると十分な固有性が満たされているとは言えないだろう。

 いずれにしても,本来は利用者自身の秘密鍵を用いて電子署名をすべきところを,サービス提供者の秘密鍵で電子署名をするように変更し,利用者の認証はパスワードとトークンなどのようなより弱いものになってしまっている点において,本来の電子署名よりは脆弱なものと評価せざるを得ない。