blob: fc45c7b611522138d0080542b716085438deff99 [file] [log] [blame]
// Copyright (c) 2020, 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 'dart:io';
import 'package:kernel/ast.dart';
import 'package:kernel/binary/ast_from_binary.dart';
import 'package:kernel/binary/ast_to_binary.dart';
main() {
final Library lib1 = new Library(Uri.parse('org-dartlang:///lib.dart'));
final Field field = new Field(new Name("f"));
lib1.addField(field);
final Block libProcedureBody = new Block([
new ExpressionStatement(new StaticSet(field, new IntLiteral(42))),
new ReturnStatement(new StaticGet(field)),
]);
final Procedure libProcedure = new Procedure(
new Name("method"),
ProcedureKind.Method,
new FunctionNode(libProcedureBody, returnType: new DynamicType()));
lib1.addProcedure(libProcedure);
final Library lib2 = new Library(Uri.parse('org-dartlang:///lib2.dart'));
final Block lib2ProcedureBody = new Block([
new ExpressionStatement(new StaticSet(field, new IntLiteral(43))),
new ReturnStatement(new StaticGet(field)),
]);
final Procedure lib2Procedure = new Procedure(
new Name("method"),
ProcedureKind.Method,
new FunctionNode(lib2ProcedureBody, returnType: new DynamicType()));
lib2.addProcedure(lib2Procedure);
verifyTargets(libProcedure, lib2Procedure, field, field);
List<int> writtenBytesFieldOriginal = serialize(lib1, lib2);
// Canonical names are now set: Verify that the field is marked as such,
// canonical-name-wise.
if (field.getterCanonicalName.parent.name != "@fields") {
throw "Expected @fields parent, but had "
"${field.getterCanonicalName.parent.name}";
}
if (field.setterCanonicalName.parent.name != "@=fields") {
throw "Expected @=fields parent, but had "
"${field.setterCanonicalName.parent.name}";
}
// Replace the field with a setter/getter pair.
lib1.fields.remove(field);
FunctionNode getterFunction = new FunctionNode(new Block([]));
Procedure getter = new Procedure(
new Name("f"), ProcedureKind.Getter, getterFunction,
reference: field.getterReference);
// Important: Unbind any old canonical name
// (nulling out the canonical name is not enough because it leaves the old
// canonical name (which always stays alive) with a pointer to the reference,
// meaning that if one tried to rebind it (e.g. if going back to a field from
// a setter/getter), the reference wouldn't (because of the way `bindTo` is
// implemented) actually have it's canonical name set, and serialization
// wouldn't work.)
field.getterReference?.canonicalName?.unbind();
lib1.addProcedure(getter);
FunctionNode setterFunction = new FunctionNode(new Block([]),
positionalParameters: [new VariableDeclaration("foo")]);
Procedure setter = new Procedure(
new Name("f"), ProcedureKind.Setter, setterFunction,
reference: field.setterReference);
// Important: Unbind any old canonical name
// (nulling out the canonical name is not enough, see above).
field.setterReference?.canonicalName?.unbind();
lib1.addProcedure(setter);
verifyTargets(libProcedure, lib2Procedure, getter, setter);
List<int> writtenBytesGetterSetter = serialize(lib1, lib2);
// Canonical names are now set: Verify that the getter/setter is marked as
// such, canonical-name-wise.
if (getter.canonicalName.parent.name != "@getters") {
throw "Expected @getters parent, but had "
"${getter.canonicalName.parent.name}";
}
if (setter.canonicalName.parent.name != "@setters") {
throw "Expected @setters parent, but had "
"${setter.canonicalName.parent.name}";
}
// Replace getter/setter with field.
lib1.procedures.remove(getter);
lib1.procedures.remove(setter);
final Field fieldReplacement = new Field(new Name("f"),
getterReference: getter.reference, setterReference: setter.reference);
// Important: Unbind any old canonical name
// (nulling out the canonical name is not enough, see above).
fieldReplacement.getterReference?.canonicalName?.unbind();
fieldReplacement.setterReference?.canonicalName?.unbind();
lib1.addField(fieldReplacement);
verifyTargets(
libProcedure, lib2Procedure, fieldReplacement, fieldReplacement);
List<int> writtenBytesFieldNew = serialize(lib1, lib2);
// Canonical names are now set: Verify that the field is marked as such,
// canonical-name-wise.
if (fieldReplacement.getterCanonicalName.parent.name != "@fields") {
throw "Expected @fields parent, but had "
"${fieldReplacement.getterCanonicalName.parent.name}";
}
if (fieldReplacement.setterCanonicalName.parent.name != "@=fields") {
throw "Expected @=fields parent, but had "
"${fieldReplacement.setterCanonicalName.parent.name}";
}
// Load the written stuff and ensure it is as expected.
// First one has a field.
Component componentLoaded = new Component();
new BinaryBuilder(writtenBytesFieldOriginal)
.readSingleFileComponent(componentLoaded);
verifyTargets(
componentLoaded.libraries[0].procedures.single,
componentLoaded.libraries[1].procedures.single,
componentLoaded.libraries[0].fields.single,
componentLoaded.libraries[0].fields.single);
// Second one has a getter/setter pair.
componentLoaded = new Component();
new BinaryBuilder(writtenBytesGetterSetter)
.readSingleFileComponent(componentLoaded);
assert(componentLoaded.libraries[0].procedures[2].isSetter);
verifyTargets(
componentLoaded.libraries[0].procedures[0],
componentLoaded.libraries[1].procedures[0],
componentLoaded.libraries[0].procedures[1],
componentLoaded.libraries[0].procedures[2]);
// Third one has a field again.
componentLoaded = new Component();
new BinaryBuilder(writtenBytesFieldNew)
.readSingleFileComponent(componentLoaded);
verifyTargets(
componentLoaded.libraries[0].procedures.single,
componentLoaded.libraries[1].procedures.single,
componentLoaded.libraries[0].fields.single,
componentLoaded.libraries[0].fields.single);
}
void verifyTargets(Procedure libProcedure, Procedure lib2Procedure,
Member getterTarget, Member setterTarget) {
if (getGetTarget(libProcedure) != getterTarget) {
throw "Unexpected get target for lib #1";
}
if (getSetTarget(libProcedure) != setterTarget) {
throw "Unexpected set target for lib #1";
}
if (getGetTarget(lib2Procedure) != getterTarget) {
throw "Unexpected get target for lib #2";
}
if (getSetTarget(lib2Procedure) != setterTarget) {
throw "Unexpected set target for lib #2";
}
}
List<int> serialize(Library lib1, Library lib2) {
Component component = new Component(libraries: [lib1, lib2])
..setMainMethodAndMode(null, false, NonNullableByDefaultCompiledMode.Weak);
ByteSink sink = new ByteSink();
new BinaryPrinter(sink).writeComponentFile(component);
return sink.builder.takeBytes();
}
Member getSetTarget(Procedure p) {
Block block = p.function.body;
ExpressionStatement getterStatement = block.statements[0];
StaticSet staticSet = getterStatement.expression;
return staticSet.target;
}
Member getGetTarget(Procedure p) {
Block block = p.function.body;
ReturnStatement setterStatement = block.statements[1];
StaticGet staticGet = setterStatement.expression;
return staticGet.target;
}
/// A [Sink] that directly writes data into a byte builder.
class ByteSink implements Sink<List<int>> {
final BytesBuilder builder = new BytesBuilder();
void add(List<int> data) {
builder.add(data);
}
void close() {}
}