// Copyright 2018 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/torque/torque-compiler.h" #include "src/torque/utils.h" #include "test/unittests/test-utils.h" #include "testing/gmock-support.h" namespace v8 { namespace internal { namespace torque { namespace { // This is a simplified version of the basic Torque type definitions. // Some class types are replaced by abstact types to keep it self-contained and // small. constexpr const char* kTestTorquePrelude = R"( type void; type never; namespace torque_internal { struct Reference { const object: HeapObject; const offset: intptr; } type ConstReference extends Reference; type MutableReference extends ConstReference; type UninitializedHeapObject extends HeapObject; macro DownCastForTorqueClass(o: HeapObject): T labels _CastError { return %RawDownCast(o); } macro IsWithContext(o: HeapObject): bool { return false; } } type Tagged generates 'TNode' constexpr 'MaybeObject'; type StrongTagged extends Tagged generates 'TNode' constexpr 'ObjectPtr'; type Smi extends StrongTagged generates 'TNode' constexpr 'Smi'; type WeakHeapObject extends Tagged; type Weak extends WeakHeapObject; type Uninitialized extends Tagged; type TaggedIndex extends StrongTagged; type TaggedZeroPattern extends TaggedIndex; @abstract @doNotGenerateCppClass extern class HeapObject extends StrongTagged { map: Map; } type Map extends HeapObject generates 'TNode'; type Object = Smi | HeapObject; type Number = Smi|HeapNumber; type JSReceiver extends HeapObject generates 'TNode'; type JSObject extends JSReceiver generates 'TNode'; type int32 generates 'TNode' constexpr 'int32_t'; type uint32 generates 'TNode' constexpr 'uint32_t'; type int31 extends int32 generates 'TNode' constexpr 'int31_t'; type uint31 extends uint32 generates 'TNode' constexpr 'uint31_t'; type int16 extends int31 generates 'TNode' constexpr 'int16_t'; type uint16 extends uint31 generates 'TNode' constexpr 'uint16_t'; type int8 extends int16 generates 'TNode' constexpr 'int8_t'; type uint8 extends uint16 generates 'TNode' constexpr 'uint8_t'; type int64 generates 'TNode' constexpr 'int64_t'; type intptr generates 'TNode' constexpr 'intptr_t'; type uintptr generates 'TNode' constexpr 'uintptr_t'; type float32 generates 'TNode' constexpr 'float'; type float64 generates 'TNode' constexpr 'double'; type bool generates 'TNode' constexpr 'bool'; type bint generates 'TNode' constexpr 'BInt'; type string constexpr 'const char*'; type RawPtr generates 'TNode' constexpr 'void*'; type ExternalPointer generates 'TNode' constexpr 'ExternalPointer_t'; type Code extends HeapObject generates 'TNode'; type BuiltinPtr extends Smi generates 'TNode'; type Context extends HeapObject generates 'TNode'; type NativeContext extends Context; type SmiTagged extends Smi; type String extends HeapObject; type HeapNumber extends HeapObject; type FixedArrayBase extends HeapObject; type Lazy; struct float64_or_hole { is_hole: bool; value: float64; } extern operator '+' macro IntPtrAdd(intptr, intptr): intptr; extern operator '!' macro Word32BinaryNot(bool): bool; extern operator '==' macro Word32Equal(int32, int32): bool; intrinsic %FromConstexpr(b: From): To; intrinsic %RawDownCast(x: From): To; intrinsic %RawConstexprCast(f: From): To; extern macro SmiConstant(constexpr Smi): Smi; extern macro TaggedToSmi(Object): Smi labels CastError; extern macro TaggedToHeapObject(Object): HeapObject labels CastError; extern macro Float64SilenceNaN(float64): float64; extern macro IntPtrConstant(constexpr int31): intptr; macro FromConstexpr(o: From): To; FromConstexpr(s: constexpr Smi): Smi { return SmiConstant(s); } FromConstexpr(s: constexpr int31): Smi { return %FromConstexpr(s); } FromConstexpr(i: constexpr int31): intptr { return IntPtrConstant(i); } FromConstexpr(i: constexpr intptr): intptr { return %FromConstexpr(i); } extern macro BoolConstant(constexpr bool): bool; FromConstexpr(b: constexpr bool): bool { return BoolConstant(b); } FromConstexpr(i: constexpr int31): int32 { return %FromConstexpr(i); } macro Cast(implicit context: Context)(o: Object): A labels CastError { return Cast(TaggedToHeapObject(o) otherwise CastError) otherwise CastError; } macro Cast(o: HeapObject): A labels CastError; Cast(o: Object): Smi labels CastError { return TaggedToSmi(o) otherwise CastError; } )"; TorqueCompilerResult TestCompileTorque(std::string source) { TorqueCompilerOptions options; options.output_directory = ""; options.collect_language_server_data = false; options.force_assert_statements = false; options.v8_root = "."; source = kTestTorquePrelude + source; return CompileTorque(source, options); } void ExpectSuccessfulCompilation(std::string source) { TorqueCompilerResult result = TestCompileTorque(std::move(source)); std::vector messages; for (const auto& message : result.messages) { messages.push_back(message.message); } EXPECT_EQ(messages, std::vector{}); } template using MatcherVector = std::vector, LineAndColumn>>; template void ExpectFailingCompilation(std::string source, MatcherVector message_patterns) { TorqueCompilerResult result = TestCompileTorque(std::move(source)); ASSERT_FALSE(result.messages.empty()); EXPECT_GE(result.messages.size(), message_patterns.size()); size_t limit = message_patterns.size(); if (result.messages.size() < limit) { limit = result.messages.size(); } for (size_t i = 0; i < limit; ++i) { EXPECT_THAT(result.messages[i].message, message_patterns[i].first); if (message_patterns[i].second != LineAndColumn::Invalid()) { base::Optional actual = result.messages[i].position; EXPECT_TRUE(actual.has_value()); EXPECT_EQ(actual->start, message_patterns[i].second); } } } template void ExpectFailingCompilation( std::string source, ::testing::PolymorphicMatcher message_pattern) { ExpectFailingCompilation( source, MatcherVector{{message_pattern, LineAndColumn::Invalid()}}); } // TODO(almuthanna): the definition of this function is skipped on Fuchsia // because it causes an 'unused function' exception upon buidling gn // Ticket: https://crbug.com/1028617 #if !defined(V8_TARGET_OS_FUCHSIA) int CountPreludeLines() { static int result = -1; if (result == -1) { std::string prelude(kTestTorquePrelude); result = static_cast(std::count(prelude.begin(), prelude.end(), '\n')); } return result; } #endif using SubstrWithPosition = std::pair<::testing::PolymorphicMatcher< ::testing::internal::HasSubstrMatcher>, LineAndColumn>; // TODO(almuthanna): the definition of this function is skipped on Fuchsia // because it causes an 'unused function' exception upon buidling gn // Ticket: https://crbug.com/1028617 #if !defined(V8_TARGET_OS_FUCHSIA) SubstrWithPosition SubstrTester(const std::string& message, int line, int col) { // Change line and column from 1-based to 0-based. return {::testing::HasSubstr(message), LineAndColumn{line + CountPreludeLines() - 1, col - 1}}; } #endif using SubstrVector = std::vector; } // namespace TEST(Torque, Prelude) { ExpectSuccessfulCompilation(""); } TEST(Torque, StackDeleteRange) { Stack stack = {1, 2, 3, 4, 5, 6, 7}; stack.DeleteRange(StackRange{BottomOffset{2}, BottomOffset{4}}); Stack result = {1, 2, 5, 6, 7}; ASSERT_TRUE(stack == result); } using ::testing::HasSubstr; TEST(Torque, TypeNamingConventionLintError) { ExpectFailingCompilation(R"( type foo generates 'TNode'; )", HasSubstr("\"foo\"")); } TEST(Torque, StructNamingConventionLintError) { ExpectFailingCompilation(R"( struct foo {} )", HasSubstr("\"foo\"")); } TEST(Torque, ClassDefinition) { ExpectSuccessfulCompilation(R"( extern class TestClassWithAllTypes extends HeapObject { a: int8; b: uint8; b2: uint8; b3: uint8; c: int16; d: uint16; e: int32; f: uint32; g: RawPtr; h: intptr; i: uintptr; } @export macro TestClassWithAllTypesLoadsAndStores( t: TestClassWithAllTypes, r: RawPtr, v1: int8, v2: uint8, v3: int16, v4: uint16, v5: int32, v6: uint32, v7: intptr, v8: uintptr) { t.a = v1; t.b = v2; t.c = v3; t.d = v4; t.e = v5; t.f = v6; t.g = r; t.h = v7; t.i = v8; t.a = t.a; t.b = t.b; t.c = t.c; t.d = t.d; t.e = t.e; t.f = t.f; t.g = t.g; t.h = t.h; t.i = t.i; } )"); } TEST(Torque, TypeDeclarationOrder) { ExpectSuccessfulCompilation(R"( type Baztype = Foo | FooType; @abstract extern class Foo extends HeapObject { fooField: FooType; } extern class Bar extends Foo { barField: Bartype; bazfield: Baztype; } type Bartype = FooType; type FooType = Smi | Bar; )"); } // TODO(almuthanna): These tests were skipped because they cause a crash when // they are ran on Fuchsia. This issue should be solved later on // Ticket: https://crbug.com/1028617 #if !defined(V8_TARGET_OS_FUCHSIA) TEST(Torque, ConditionalFields) { // This class should throw alignment errors if @if decorators aren't // working. ExpectSuccessfulCompilation(R"( extern class PreprocessingTest extends HeapObject { @if(FALSE_FOR_TESTING) a: int8; @if(TRUE_FOR_TESTING) a: int16; b: int16; d: int32; @ifnot(TRUE_FOR_TESTING) e: int8; @ifnot(FALSE_FOR_TESTING) f: int16; g: int16; h: int32; } )"); ExpectFailingCompilation(R"( extern class PreprocessingTest extends HeapObject { @if(TRUE_FOR_TESTING) a: int8; @if(FALSE_FOR_TESTING) a: int16; b: int16; d: int32; @ifnot(FALSE_FOR_TESTING) e: int8; @ifnot(TRUE_FOR_TESTING) f: int16; g: int16; h: int32; } )", HasSubstr("aligned")); } TEST(Torque, ConstexprLetBindingDoesNotCrash) { ExpectFailingCompilation( R"(@export macro FooBar() { let foo = 0; check(foo >= 0); })", HasSubstr("Use 'const' instead of 'let' for variable 'foo'")); } TEST(Torque, FailedImplicitCastFromConstexprDoesNotCrash) { ExpectFailingCompilation( R"( extern enum SomeEnum { kValue, ... } macro Foo() { Bar(SomeEnum::kValue); } macro Bar(value: T) {} )", HasSubstr( "Cannot find non-constexpr type corresponding to constexpr kValue")); } TEST(Torque, DoubleUnderScorePrefixIllegalForIdentifiers) { ExpectFailingCompilation(R"( @export macro Foo() { let __x; } )", HasSubstr("Lexer Error")); } #endif TEST(Torque, UnusedLetBindingLintError) { ExpectFailingCompilation(R"( @export macro Foo(y: Smi) { let x: Smi = y; } )", HasSubstr("Variable 'x' is never used.")); } TEST(Torque, UnderscorePrefixSilencesUnusedWarning) { ExpectSuccessfulCompilation(R"( @export macro Foo(y: Smi) { let _x: Smi = y; } )"); } // TODO(almuthanna): This test was skipped because it causes a crash when it is // ran on Fuchsia. This issue should be solved later on // Ticket: https://crbug.com/1028617 #if !defined(V8_TARGET_OS_FUCHSIA) TEST(Torque, UsingUnderscorePrefixedIdentifierError) { ExpectFailingCompilation(R"( @export macro Foo(y: Smi) { let _x: Smi = y; check(_x == y); } )", HasSubstr("Trying to reference '_x'")); } #endif TEST(Torque, UnusedArgumentLintError) { ExpectFailingCompilation(R"( @export macro Foo(x: Smi) {} )", HasSubstr("Variable 'x' is never used.")); } TEST(Torque, UsingUnderscorePrefixedArgumentSilencesWarning) { ExpectSuccessfulCompilation(R"( @export macro Foo(_y: Smi) {} )"); } TEST(Torque, UnusedLabelLintError) { ExpectFailingCompilation(R"( @export macro Foo() labels Bar {} )", HasSubstr("Label 'Bar' is never used.")); } TEST(Torque, UsingUnderScorePrefixLabelSilencesWarning) { ExpectSuccessfulCompilation(R"( @export macro Foo() labels _Bar {} )"); } TEST(Torque, NoUnusedWarningForImplicitArguments) { ExpectSuccessfulCompilation(R"( @export macro Foo(implicit c: Context, r: JSReceiver)() {} )"); } TEST(Torque, NoUnusedWarningForVariablesOnlyUsedInAsserts) { ExpectSuccessfulCompilation(R"( @export macro Foo(x: bool) { assert(x); } )"); } // TODO(almuthanna): This test was skipped because it causes a crash when it is // ran on Fuchsia. This issue should be solved later on // Ticket: https://crbug.com/1028617 #if !defined(V8_TARGET_OS_FUCHSIA) TEST(Torque, ImportNonExistentFile) { ExpectFailingCompilation(R"(import "foo/bar.tq")", HasSubstr("File 'foo/bar.tq' not found.")); } #endif TEST(Torque, LetShouldBeConstLintError) { ExpectFailingCompilation(R"( @export macro Foo(y: Smi): Smi { let x: Smi = y; return x; })", HasSubstr("Variable 'x' is never assigned to.")); } TEST(Torque, LetShouldBeConstIsSkippedForStructs) { ExpectSuccessfulCompilation(R"( struct Foo{ a: Smi; } @export macro Bar(x: Smi): Foo { let foo = Foo{a: x}; return foo; } )"); } // TODO(almuthanna): These tests were skipped because they cause a crash when // they are ran on Fuchsia. This issue should be solved later on // Ticket: https://crbug.com/1028617 #if !defined(V8_TARGET_OS_FUCHSIA) TEST(Torque, GenericAbstractType) { ExpectSuccessfulCompilation(R"( type Foo extends HeapObject; extern macro F1(HeapObject); macro F2(x: Foo) { F1(x); } @export macro F3(a: Foo, b: Foo){ F2(a); F2(b); } )"); ExpectFailingCompilation(R"( type Foo extends HeapObject; macro F1(x: Foo) {} @export macro F2(a: Foo) { F1(a); })", HasSubstr("cannot find suitable callable")); ExpectFailingCompilation(R"( type Foo extends HeapObject; extern macro F1(Foo); @export macro F2(a: Foo) { F1(a); })", HasSubstr("cannot find suitable callable")); } TEST(Torque, SpecializationRequesters) { ExpectFailingCompilation( R"( macro A() {} macro B() { A(); } macro C() { B(); } macro D() { C(); } )", SubstrVector{ SubstrTester("cannot find suitable callable", 4, 7), SubstrTester("Note: in specialization B requested here", 7, 7), SubstrTester("Note: in specialization C requested here", 10, 7)}); ExpectFailingCompilation( R"( extern macro RetVal(): Object; builtin A(implicit context: Context)(): Object { return RetVal(); } builtin B(implicit context: Context)(): Object { return A(); } builtin C(implicit context: Context)(): Object { return B(); } builtin D(implicit context: Context)(): Object { return C(); } )", SubstrVector{ SubstrTester("cannot find suitable callable", 7, 14), SubstrTester("Note: in specialization B requested here", 10, 14), SubstrTester("Note: in specialization C requested here", 13, 14)}); ExpectFailingCompilation( R"( struct A {} struct B { a: A; } struct C { b: B; } struct D { c: C; } )", SubstrVector{ SubstrTester("Could not instantiate generic", 4, 10), SubstrTester("Note: in specialization B requested here", 7, 10), SubstrTester("Note: in specialization C requested here", 10, 10)}); ExpectFailingCompilation( R"( macro A() {} macro B() { A(); } struct C { macro Method() { B(); } } macro D(_b: C) {} )", SubstrVector{ SubstrTester("cannot find suitable callable", 4, 7), SubstrTester("Note: in specialization B requested here", 8, 9), SubstrTester("Note: in specialization C requested here", 11, 5)}); } #endif TEST(Torque, Enums) { ExpectSuccessfulCompilation(R"( extern enum MyEnum { kValue0, kValue1, kValue2, kValue3 } )"); ExpectFailingCompilation(R"( extern enum MyEmptyEnum { } )", HasSubstr("unexpected token \"}\"")); } TEST(Torque, EnumInTypeswitch) { ExpectSuccessfulCompilation(R"( extern enum MyEnum extends Smi { kA, kB, kC } @export macro Test(implicit context: Context)(v : MyEnum): Smi { typeswitch(v) { case (MyEnum::kA | MyEnum::kB): { return 1; } case (MyEnum::kC): { return 2; } } } )"); ExpectSuccessfulCompilation(R"( extern enum MyEnum extends Smi { kA, kB, kC, ... } @export macro Test(implicit context: Context)(v : MyEnum): Smi { typeswitch(v) { case (MyEnum::kC): { return 2; } case (MyEnum::kA | MyEnum::kB): { return 1; } case (MyEnum): { return 0; } } } )"); ExpectSuccessfulCompilation(R"( extern enum MyEnum extends Smi { kA, kB, kC, ... } @export macro Test(implicit context: Context)(b: bool): Smi { return b ? MyEnum::kB : MyEnum::kA; } )"); } TEST(Torque, EnumTypeAnnotations) { ExpectSuccessfulCompilation(R"( type Type1 extends intptr; type Type2 extends intptr; extern enum MyEnum extends intptr { kValue1: Type1, kValue2: Type2, kValue3 } @export macro Foo() { const _a: Type1 = MyEnum::kValue1; const _b: Type2 = MyEnum::kValue2; const _c: intptr = MyEnum::kValue3; } )"); } TEST(Torque, ConstClassFields) { ExpectSuccessfulCompilation(R"( class Foo extends HeapObject { const x: int32; y: int32; } @export macro Test(implicit context: Context)(o: Foo, n: int32) { const _x: int32 = o.x; o.y = n; } )"); ExpectFailingCompilation(R"( class Foo extends HeapObject { const x: int32; } @export macro Test(implicit context: Context)(o: Foo, n: int32) { o.x = n; } )", HasSubstr("cannot assign to const value")); ExpectSuccessfulCompilation(R"( class Foo extends HeapObject { s: Bar; } struct Bar { const x: int32; y: int32; } @export macro Test(implicit context: Context)(o: Foo, n: int32) { const _x: int32 = o.s.x; // Assigning a struct as a value is OK, even when the struct contains // const fields. o.s = Bar{x: n, y: n}; o.s.y = n; } )"); ExpectFailingCompilation(R"( class Foo extends HeapObject { const s: Bar; } struct Bar { const x: int32; y: int32; } @export macro Test(implicit context: Context)(o: Foo, n: int32) { o.s.y = n; } )", HasSubstr("cannot assign to const value")); ExpectFailingCompilation(R"( class Foo extends HeapObject { s: Bar; } struct Bar { const x: int32; y: int32; } @export macro Test(implicit context: Context)(o: Foo, n: int32) { o.s.x = n; } )", HasSubstr("cannot assign to const value")); } TEST(Torque, References) { ExpectSuccessfulCompilation(R"( class Foo extends HeapObject { const x: int32; y: int32; } @export macro Test(implicit context: Context)(o: Foo, n: int32) { const constRefX: const &int32 = &o.x; const refY: &int32 = &o.y; const constRefY: const &int32 = refY; const _x: int32 = *constRefX; const _y1: int32 = *refY; const _y2: int32 = *constRefY; *refY = n; let r: const &int32 = constRefX; r = constRefY; } )"); ExpectFailingCompilation(R"( class Foo extends HeapObject { const x: int32; y: int32; } @export macro Test(implicit context: Context)(o: Foo) { const _refX: &int32 = &o.x; } )", HasSubstr("cannot use expression of type const " "&int32 as a value of type &int32")); ExpectFailingCompilation(R"( class Foo extends HeapObject { const x: int32; y: int32; } @export macro Test(implicit context: Context)(o: Foo, n: int32) { const constRefX: const &int32 = &o.x; *constRefX = n; } )", HasSubstr("cannot assign to const value")); } TEST(Torque, CatchFirstHandler) { ExpectFailingCompilation( R"( @export macro Test() { try { } label Foo { } catch (e) {} } )", HasSubstr( "catch handler always has to be first, before any label handler")); } TEST(Torque, BitFieldLogicalAnd) { std::string prelude = R"( bitfield struct S extends uint32 { a: bool: 1 bit; b: bool: 1 bit; c: int32: 5 bit; } macro Test(s: S): bool { return )"; std::string postlude = ";}"; std::string message = "use & rather than &&"; ExpectFailingCompilation(prelude + "s.a && s.b" + postlude, HasSubstr(message)); ExpectFailingCompilation(prelude + "s.a && !s.b" + postlude, HasSubstr(message)); ExpectFailingCompilation(prelude + "!s.b && s.c == 34" + postlude, HasSubstr(message)); } TEST(Torque, FieldAccessOnNonClassType) { ExpectFailingCompilation( R"( @export macro Test(x: Number): Map { return x.map; } )", HasSubstr("map")); } TEST(Torque, UnusedImplicit) { ExpectSuccessfulCompilation(R"( @export macro Test1(implicit c: Smi)(a: Object): Object { return a; } @export macro Test2(b: Object) { Test1(b); } )"); ExpectFailingCompilation( R"( macro Test1(implicit c: Smi)(_a: Object): Smi { return c; } @export macro Test2(b: Smi) { Test1(b); } )", HasSubstr("undefined expression of type Smi: the implicit " "parameter 'c' is not defined when invoking Test1 at")); ExpectFailingCompilation( R"( extern macro Test3(implicit c: Smi)(Object): Smi; @export macro Test4(b: Smi) { Test3(b); } )", HasSubstr("unititialized implicit parameters can only be passed to " "Torque-defined macros: the implicit parameter 'c' is not " "defined when invoking Test3")); ExpectSuccessfulCompilation( R"( macro Test7(implicit c: Smi)(o: T): Smi; Test7(implicit c: Smi)(o: Smi): Smi { return o; } @export macro Test8(b: Smi) { Test7(b); } )"); ExpectFailingCompilation( R"( macro Test6(_o: T): T; macro Test6(implicit c: T)(_o: T): T { return c; } macro Test7(o: T): Smi; Test7(o: Smi): Smi { return Test6(o); } @export macro Test8(b: Smi) { Test7(b); } )", HasSubstr("\nambiguous callable : \n Test6(Smi)\ncandidates are:\n " "Test6(Smi): Smi\n Test6(implicit Smi)(Smi): Smi")); } TEST(Torque, ImplicitTemplateParameterInference) { ExpectSuccessfulCompilation(R"( macro Foo(_x: Map) {} macro Foo(_x: Smi) {} macro GenericMacro(implicit x: T)() { Foo(x); } @export macro Test1(implicit x: Smi)() { GenericMacro(); } @export macro Test2(implicit x: Map)() { GenericMacro(); } )"); ExpectFailingCompilation( R"( // Wrap in namespace to avoid redeclaration error. namespace foo { macro Foo(implicit x: Map)() {} } macro Foo(implicit x: Smi)() {} namespace foo{ @export macro Test(implicit x: Smi)() { Foo(); } } )", HasSubstr("ambiguous callable")); ExpectFailingCompilation( R"( // Wrap in namespace to avoid redeclaration error. namespace foo { macro Foo(implicit x: Map)() {} } macro Foo(implicit x: Smi)() {} namespace foo{ @export macro Test(implicit x: Map)() { Foo(); } } )", HasSubstr("ambiguous callable")); } } // namespace torque } // namespace internal } // namespace v8