Commit 4f15a008 authored by ager@chromium.org's avatar ager@chromium.org

Make sure that the generic stubs for keyed load and store and for

dictionary probing respects access check bit.
Review URL: http://codereview.chromium.org/13663

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@955 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 90611c0e
...@@ -317,8 +317,8 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) { ...@@ -317,8 +317,8 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) {
__ b(eq, &miss); __ b(eq, &miss);
// Check that the receiver is a valid JS object. // Check that the receiver is a valid JS object.
__ ldr(r0, FieldMemOperand(r1, HeapObject::kMapOffset)); __ ldr(r3, FieldMemOperand(r1, HeapObject::kMapOffset));
__ ldrb(r0, FieldMemOperand(r0, Map::kInstanceTypeOffset)); __ ldrb(r0, FieldMemOperand(r3, Map::kInstanceTypeOffset));
__ cmp(r0, Operand(FIRST_JS_OBJECT_TYPE)); __ cmp(r0, Operand(FIRST_JS_OBJECT_TYPE));
__ b(lt, &miss); __ b(lt, &miss);
...@@ -333,6 +333,10 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) { ...@@ -333,6 +333,10 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) {
// Accessing global object: Load and invoke. // Accessing global object: Load and invoke.
__ bind(&global_object); __ bind(&global_object);
// Check that the global object does not require access checks.
__ ldrb(r3, FieldMemOperand(r3, Map::kBitFieldOffset));
__ tst(r3, Operand(1 << Map::kIsAccessCheckNeeded));
__ b(ne, &miss);
GenerateNormalHelper(masm, argc, true, &miss); GenerateNormalHelper(masm, argc, true, &miss);
// Accessing non-global object: Check for access to global proxy. // Accessing non-global object: Check for access to global proxy.
...@@ -340,6 +344,11 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) { ...@@ -340,6 +344,11 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) {
__ bind(&non_global_object); __ bind(&non_global_object);
__ cmp(r0, Operand(JS_GLOBAL_PROXY_TYPE)); __ cmp(r0, Operand(JS_GLOBAL_PROXY_TYPE));
__ b(eq, &global_proxy); __ b(eq, &global_proxy);
// Check that the non-global, non-global-proxy object does not
// require access checks.
__ ldrb(r3, FieldMemOperand(r3, Map::kBitFieldOffset));
__ tst(r3, Operand(1 << Map::kIsAccessCheckNeeded));
__ b(ne, &miss);
__ bind(&invoke); __ bind(&invoke);
GenerateNormalHelper(masm, argc, false, &miss); GenerateNormalHelper(masm, argc, false, &miss);
...@@ -441,8 +450,8 @@ void LoadIC::GenerateNormal(MacroAssembler* masm) { ...@@ -441,8 +450,8 @@ void LoadIC::GenerateNormal(MacroAssembler* masm) {
__ b(eq, &miss); __ b(eq, &miss);
// Check that the receiver is a valid JS object. // Check that the receiver is a valid JS object.
__ ldr(r1, FieldMemOperand(r0, HeapObject::kMapOffset)); __ ldr(r3, FieldMemOperand(r0, HeapObject::kMapOffset));
__ ldrb(r1, FieldMemOperand(r1, Map::kInstanceTypeOffset)); __ ldrb(r1, FieldMemOperand(r3, Map::kInstanceTypeOffset));
__ cmp(r1, Operand(FIRST_JS_OBJECT_TYPE)); __ cmp(r1, Operand(FIRST_JS_OBJECT_TYPE));
__ b(lt, &miss); __ b(lt, &miss);
// If this assert fails, we have to check upper bound too. // If this assert fails, we have to check upper bound too.
...@@ -452,6 +461,11 @@ void LoadIC::GenerateNormal(MacroAssembler* masm) { ...@@ -452,6 +461,11 @@ void LoadIC::GenerateNormal(MacroAssembler* masm) {
__ cmp(r1, Operand(JS_GLOBAL_PROXY_TYPE)); __ cmp(r1, Operand(JS_GLOBAL_PROXY_TYPE));
__ b(eq, &global); __ b(eq, &global);
// Check for non-global object that requires access check.
__ ldrb(r3, FieldMemOperand(r3, Map::kBitFieldOffset));
__ tst(r3, Operand(1 << Map::kIsAccessCheckNeeded));
__ b(ne, &miss);
__ bind(&probe); __ bind(&probe);
GenerateDictionaryLoad(masm, &miss, r1, r0); GenerateDictionaryLoad(masm, &miss, r1, r0);
GenerateCheckNonFunctionOrLoaded(masm, &miss, r0, r1); GenerateCheckNonFunctionOrLoaded(masm, &miss, r0, r1);
...@@ -525,12 +539,19 @@ void KeyedLoadIC::GenerateGeneric(MacroAssembler* masm) { ...@@ -525,12 +539,19 @@ void KeyedLoadIC::GenerateGeneric(MacroAssembler* masm) {
__ tst(r1, Operand(kSmiTagMask)); __ tst(r1, Operand(kSmiTagMask));
__ b(eq, &slow); __ b(eq, &slow);
// Get the map of the receiver.
__ ldr(r2, FieldMemOperand(r1, HeapObject::kMapOffset));
// Check that the receiver does not require access checks. We need
// to check this explicitly since this generic stub does not perform
// map checks.
__ ldrb(r3, FieldMemOperand(r2, Map::kBitFieldOffset));
__ tst(r3, Operand(1 << Map::kIsAccessCheckNeeded));
__ b(ne, &slow);
// Check that the object is some kind of JS object EXCEPT JS Value type. // Check that the object is some kind of JS object EXCEPT JS Value type.
// In the case that the object is a value-wrapper object, // In the case that the object is a value-wrapper object,
// we enter the runtime system to make sure that indexing into string // we enter the runtime system to make sure that indexing into string
// objects work as intended. // objects work as intended.
ASSERT(JS_OBJECT_TYPE > JS_VALUE_TYPE); ASSERT(JS_OBJECT_TYPE > JS_VALUE_TYPE);
__ ldr(r2, FieldMemOperand(r1, HeapObject::kMapOffset));
__ ldrb(r2, FieldMemOperand(r2, Map::kInstanceTypeOffset)); __ ldrb(r2, FieldMemOperand(r2, Map::kInstanceTypeOffset));
__ cmp(r2, Operand(JS_OBJECT_TYPE)); __ cmp(r2, Operand(JS_OBJECT_TYPE));
__ b(lt, &slow); __ b(lt, &slow);
...@@ -597,10 +618,15 @@ void KeyedStoreIC::GenerateGeneric(MacroAssembler* masm) { ...@@ -597,10 +618,15 @@ void KeyedStoreIC::GenerateGeneric(MacroAssembler* masm) {
// Check that the object isn't a smi. // Check that the object isn't a smi.
__ tst(r3, Operand(kSmiTagMask)); __ tst(r3, Operand(kSmiTagMask));
__ b(eq, &slow); __ b(eq, &slow);
// Get the type of the object from its map. // Get the map of the object.
__ ldr(r2, FieldMemOperand(r3, HeapObject::kMapOffset)); __ ldr(r2, FieldMemOperand(r3, HeapObject::kMapOffset));
__ ldrb(r2, FieldMemOperand(r2, Map::kInstanceTypeOffset)); // Check that the receiver does not require access checks. We need
// to do this because this generic stub does not perform map checks.
__ ldrb(ip, FieldMemOperand(r2, Map::kBitFieldOffset));
__ tst(ip, Operand(1 << Map::kIsAccessCheckNeeded));
__ b(ne, &slow);
// Check if the object is a JS array or not. // Check if the object is a JS array or not.
__ ldrb(r2, FieldMemOperand(r2, Map::kInstanceTypeOffset));
__ cmp(r2, Operand(JS_ARRAY_TYPE)); __ cmp(r2, Operand(JS_ARRAY_TYPE));
// r1 == key. // r1 == key.
__ b(eq, &array); __ b(eq, &array);
......
...@@ -215,18 +215,27 @@ void KeyedLoadIC::GenerateGeneric(MacroAssembler* masm) { ...@@ -215,18 +215,27 @@ void KeyedLoadIC::GenerateGeneric(MacroAssembler* masm) {
// ----------------------------------- // -----------------------------------
Label slow, fast, check_string, index_int, index_string; Label slow, fast, check_string, index_int, index_string;
// Load name and receiver.
__ mov(eax, (Operand(esp, kPointerSize))); __ mov(eax, (Operand(esp, kPointerSize)));
__ mov(ecx, (Operand(esp, 2 * kPointerSize))); __ mov(ecx, (Operand(esp, 2 * kPointerSize)));
// Check that the object isn't a smi. // Check that the object isn't a smi.
__ test(ecx, Immediate(kSmiTagMask)); __ test(ecx, Immediate(kSmiTagMask));
__ j(zero, &slow, not_taken); __ j(zero, &slow, not_taken);
// Get the map of the receiver.
__ mov(edx, FieldOperand(ecx, HeapObject::kMapOffset));
// Check that the receiver does not require access checks. We need
// to check this explicitly since this generic stub does not perform
// map checks.
__ movzx_b(ebx, FieldOperand(edx, Map::kBitFieldOffset));
__ test(ebx, Immediate(1 << Map::kIsAccessCheckNeeded));
__ j(not_zero, &slow, not_taken);
// Check that the object is some kind of JS object EXCEPT JS Value type. // Check that the object is some kind of JS object EXCEPT JS Value type.
// In the case that the object is a value-wrapper object, // In the case that the object is a value-wrapper object,
// we enter the runtime system to make sure that indexing // we enter the runtime system to make sure that indexing
// into string objects work as intended. // into string objects work as intended.
ASSERT(JS_OBJECT_TYPE > JS_VALUE_TYPE); ASSERT(JS_OBJECT_TYPE > JS_VALUE_TYPE);
__ mov(edx, FieldOperand(ecx, HeapObject::kMapOffset));
__ movzx_b(edx, FieldOperand(edx, Map::kInstanceTypeOffset)); __ movzx_b(edx, FieldOperand(edx, Map::kInstanceTypeOffset));
__ cmp(edx, JS_OBJECT_TYPE); __ cmp(edx, JS_OBJECT_TYPE);
__ j(less, &slow, not_taken); __ j(less, &slow, not_taken);
...@@ -268,7 +277,7 @@ void KeyedLoadIC::GenerateGeneric(MacroAssembler* masm) { ...@@ -268,7 +277,7 @@ void KeyedLoadIC::GenerateGeneric(MacroAssembler* masm) {
// bits have been subtracted to allow space for the length and the cached // bits have been subtracted to allow space for the length and the cached
// array index. // array index.
ASSERT(TenToThe(String::kMaxCachedArrayIndexLength) < ASSERT(TenToThe(String::kMaxCachedArrayIndexLength) <
(1 << (String::kShortLengthShift - String::kHashShift))); (1 << (String::kShortLengthShift - String::kHashShift)));
__ bind(&index_string); __ bind(&index_string);
const int kLengthFieldLimit = const int kLengthFieldLimit =
(String::kMaxCachedArrayIndexLength + 1) << String::kShortLengthShift; (String::kMaxCachedArrayIndexLength + 1) << String::kShortLengthShift;
...@@ -298,17 +307,25 @@ void KeyedStoreIC::GenerateGeneric(MacroAssembler* masm) { ...@@ -298,17 +307,25 @@ void KeyedStoreIC::GenerateGeneric(MacroAssembler* masm) {
// -- esp[8] : receiver // -- esp[8] : receiver
// ----------------------------------- // -----------------------------------
Label slow, fast, array, extra; Label slow, fast, array, extra;
// Get the key and the object from the stack.
__ mov(ebx, Operand(esp, 1 * kPointerSize)); // 1 ~ return address // Get the receiver from the stack.
__ mov(edx, Operand(esp, 2 * kPointerSize)); // 2 ~ return address, key __ mov(edx, Operand(esp, 2 * kPointerSize)); // 2 ~ return address, key
// Check that the key is a smi.
__ test(ebx, Immediate(kSmiTagMask));
__ j(not_zero, &slow, not_taken);
// Check that the object isn't a smi. // Check that the object isn't a smi.
__ test(edx, Immediate(kSmiTagMask)); __ test(edx, Immediate(kSmiTagMask));
__ j(zero, &slow, not_taken); __ j(zero, &slow, not_taken);
// Get the type of the object from its map. // Get the map from the receiver.
__ mov(ecx, FieldOperand(edx, HeapObject::kMapOffset)); __ mov(ecx, FieldOperand(edx, HeapObject::kMapOffset));
// Check that the receiver does not require access checks. We need
// to do this because this generic stub does not perform map checks.
__ movzx_b(ebx, FieldOperand(ecx, Map::kBitFieldOffset));
__ test(ebx, Immediate(1 << Map::kIsAccessCheckNeeded));
__ j(not_zero, &slow, not_taken);
// Get the key from the stack.
__ mov(ebx, Operand(esp, 1 * kPointerSize)); // 1 ~ return address
// Check that the key is a smi.
__ test(ebx, Immediate(kSmiTagMask));
__ j(not_zero, &slow, not_taken);
// Get the instance type from the map of the receiver.
__ movzx_b(ecx, FieldOperand(ecx, Map::kInstanceTypeOffset)); __ movzx_b(ecx, FieldOperand(ecx, Map::kInstanceTypeOffset));
// Check if the object is a JS array or not. // Check if the object is a JS array or not.
__ cmp(ecx, JS_ARRAY_TYPE); __ cmp(ecx, JS_ARRAY_TYPE);
...@@ -317,7 +334,6 @@ void KeyedStoreIC::GenerateGeneric(MacroAssembler* masm) { ...@@ -317,7 +334,6 @@ void KeyedStoreIC::GenerateGeneric(MacroAssembler* masm) {
__ cmp(ecx, FIRST_JS_OBJECT_TYPE); __ cmp(ecx, FIRST_JS_OBJECT_TYPE);
__ j(less, &slow, not_taken); __ j(less, &slow, not_taken);
// Object case: Check key against length in the elements array. // Object case: Check key against length in the elements array.
// eax: value // eax: value
// edx: JSObject // edx: JSObject
...@@ -515,8 +531,8 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) { ...@@ -515,8 +531,8 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) {
__ j(zero, &miss, not_taken); __ j(zero, &miss, not_taken);
// Check that the receiver is a valid JS object. // Check that the receiver is a valid JS object.
__ mov(eax, FieldOperand(edx, HeapObject::kMapOffset)); __ mov(ebx, FieldOperand(edx, HeapObject::kMapOffset));
__ movzx_b(eax, FieldOperand(eax, Map::kInstanceTypeOffset)); __ movzx_b(eax, FieldOperand(ebx, Map::kInstanceTypeOffset));
__ cmp(eax, FIRST_JS_OBJECT_TYPE); __ cmp(eax, FIRST_JS_OBJECT_TYPE);
__ j(less, &miss, not_taken); __ j(less, &miss, not_taken);
...@@ -531,6 +547,10 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) { ...@@ -531,6 +547,10 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) {
// Accessing global object: Load and invoke. // Accessing global object: Load and invoke.
__ bind(&global_object); __ bind(&global_object);
// Check that the global object does not require access checks.
__ movzx_b(ebx, FieldOperand(ebx, Map::kBitFieldOffset));
__ test(ebx, Immediate(1 << Map::kIsAccessCheckNeeded));
__ j(not_equal, &miss, not_taken);
GenerateNormalHelper(masm, argc, true, &miss); GenerateNormalHelper(masm, argc, true, &miss);
// Accessing non-global object: Check for access to global proxy. // Accessing non-global object: Check for access to global proxy.
...@@ -538,6 +558,11 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) { ...@@ -538,6 +558,11 @@ void CallIC::GenerateNormal(MacroAssembler* masm, int argc) {
__ bind(&non_global_object); __ bind(&non_global_object);
__ cmp(eax, JS_GLOBAL_PROXY_TYPE); __ cmp(eax, JS_GLOBAL_PROXY_TYPE);
__ j(equal, &global_proxy, not_taken); __ j(equal, &global_proxy, not_taken);
// Check that the non-global, non-global-proxy object does not
// require access checks.
__ movzx_b(ebx, FieldOperand(ebx, Map::kBitFieldOffset));
__ test(ebx, Immediate(1 << Map::kIsAccessCheckNeeded));
__ j(not_equal, &miss, not_taken);
__ bind(&invoke); __ bind(&invoke);
GenerateNormalHelper(masm, argc, false, &miss); GenerateNormalHelper(masm, argc, false, &miss);
...@@ -642,8 +667,8 @@ void LoadIC::GenerateNormal(MacroAssembler* masm) { ...@@ -642,8 +667,8 @@ void LoadIC::GenerateNormal(MacroAssembler* masm) {
__ j(zero, &miss, not_taken); __ j(zero, &miss, not_taken);
// Check that the receiver is a valid JS object. // Check that the receiver is a valid JS object.
__ mov(edx, FieldOperand(eax, HeapObject::kMapOffset)); __ mov(ebx, FieldOperand(eax, HeapObject::kMapOffset));
__ movzx_b(edx, FieldOperand(edx, Map::kInstanceTypeOffset)); __ movzx_b(edx, FieldOperand(ebx, Map::kInstanceTypeOffset));
__ cmp(edx, FIRST_JS_OBJECT_TYPE); __ cmp(edx, FIRST_JS_OBJECT_TYPE);
__ j(less, &miss, not_taken); __ j(less, &miss, not_taken);
...@@ -654,6 +679,11 @@ void LoadIC::GenerateNormal(MacroAssembler* masm) { ...@@ -654,6 +679,11 @@ void LoadIC::GenerateNormal(MacroAssembler* masm) {
__ cmp(edx, JS_GLOBAL_PROXY_TYPE); __ cmp(edx, JS_GLOBAL_PROXY_TYPE);
__ j(equal, &global, not_taken); __ j(equal, &global, not_taken);
// Check for non-global object that requires access check.
__ movzx_b(ebx, FieldOperand(ebx, Map::kBitFieldOffset));
__ test(ebx, Immediate(1 << Map::kIsAccessCheckNeeded));
__ j(not_zero, &miss, not_taken);
// Search the dictionary placing the result in eax. // Search the dictionary placing the result in eax.
__ bind(&probe); __ bind(&probe);
GenerateDictionaryLoad(masm, &miss, edx, eax, ebx, ecx); GenerateDictionaryLoad(masm, &miss, edx, eax, ebx, ecx);
......
...@@ -1733,13 +1733,19 @@ static Object* Runtime_KeyedGetProperty(Arguments args) { ...@@ -1733,13 +1733,19 @@ static Object* Runtime_KeyedGetProperty(Arguments args) {
ASSERT(args.length() == 2); ASSERT(args.length() == 2);
// Fast cases for getting named properties of the receiver JSObject // Fast cases for getting named properties of the receiver JSObject
// itself. The global proxy objects has to be excluded since // itself.
// LocalLookup on the global proxy object can return a valid result //
// eventhough the global proxy object never has properties. This is // The global proxy objects has to be excluded since LocalLookup on
// the case because the global proxy object forwards everything to // the global proxy object can return a valid result eventhough the
// its hidden prototype including local lookups. // global proxy object never has properties. This is the case
// because the global proxy object forwards everything to its hidden
// prototype including local lookups.
//
// Additionally, we need to make sure that we do not cache results
// for objects that require access checks.
if (args[0]->IsJSObject() && if (args[0]->IsJSObject() &&
!args[0]->IsJSGlobalProxy() && !args[0]->IsJSGlobalProxy() &&
!args[0]->IsAccessCheckNeeded() &&
args[1]->IsString()) { args[1]->IsString()) {
JSObject* receiver = JSObject::cast(args[0]); JSObject* receiver = JSObject::cast(args[0]);
String* key = String::cast(args[1]); String* key = String::cast(args[1]);
......
...@@ -3681,29 +3681,81 @@ TEST(AccessControlIC) { ...@@ -3681,29 +3681,81 @@ TEST(AccessControlIC) {
v8::Handle<Value> value; v8::Handle<Value> value;
// Check that the named access-control function is called every time. // Check that the named access-control function is called every time.
value = v8_compile("for (var i = 0; i < 10; i++) obj.prop = 1;")->Run(); CompileRun("function testProp(obj) {"
value = v8_compile("for (var i = 0; i < 10; i++) obj.prop;" " for (var i = 0; i < 10; i++) obj.prop = 1;"
"obj.prop")->Run(); " for (var j = 0; j < 10; j++) obj.prop;"
" return obj.prop"
"}");
value = CompileRun("testProp(obj)");
CHECK(value->IsNumber()); CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value()); CHECK_EQ(1, value->Int32Value());
CHECK_EQ(21, named_access_count); CHECK_EQ(21, named_access_count);
// Check that the named access-control function is called every time. // Check that the named access-control function is called every time.
value = v8_compile("var p = 'prop';")->Run(); CompileRun("var p = 'prop';"
value = v8_compile("for (var i = 0; i < 10; i++) obj[p] = 1;")->Run(); "function testKeyed(obj) {"
value = v8_compile("for (var i = 0; i < 10; i++) obj[p];" " for (var i = 0; i < 10; i++) obj[p] = 1;"
"obj[p]")->Run(); " for (var j = 0; j < 10; j++) obj[p];"
" return obj[p];"
"}");
// Use obj which requires access checks. No inline caching is used
// in that case.
value = CompileRun("testKeyed(obj)");
CHECK(value->IsNumber()); CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value()); CHECK_EQ(1, value->Int32Value());
CHECK_EQ(42, named_access_count); CHECK_EQ(42, named_access_count);
// Force the inline caches into generic state and try again.
CompileRun("testKeyed({ a: 0 })");
CompileRun("testKeyed({ b: 0 })");
value = CompileRun("testKeyed(obj)");
CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value());
CHECK_EQ(63, named_access_count);
// Check that the indexed access-control function is called every time. // Check that the indexed access-control function is called every time.
value = v8_compile("for (var i = 0; i < 10; i++) obj[0] = 1;")->Run(); CompileRun("function testIndexed(obj) {"
value = v8_compile("for (var i = 0; i < 10; i++) obj[0];" " for (var i = 0; i < 10; i++) obj[0] = 1;"
"obj[0]")->Run(); " for (var j = 0; j < 10; j++) obj[0];"
" return obj[0]"
"}");
value = CompileRun("testIndexed(obj)");
CHECK(value->IsNumber()); CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value()); CHECK_EQ(1, value->Int32Value());
CHECK_EQ(21, indexed_access_count); CHECK_EQ(21, indexed_access_count);
// Force the inline caches into generic state.
CompileRun("testIndexed(new Array(1))");
// Test that the indexed access check is called.
value = CompileRun("testIndexed(obj)");
CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value());
CHECK_EQ(42, indexed_access_count);
// Check that the named access check is called when invoking
// functions on an object that requires access checks.
CompileRun("obj.f = function() {}");
CompileRun("function testCallNormal(obj) {"
" for (var i = 0; i < 10; i++) obj.f();"
"}");
CompileRun("testCallNormal(obj)");
CHECK_EQ(74, named_access_count);
// Force obj into slow case.
value = CompileRun("delete obj.prop");
CHECK(value->BooleanValue());
// Force inline caches into dictionary probing mode.
CompileRun("var o = { x: 0 }; delete o.x; testProp(o);");
// Test that the named access check is called.
value = CompileRun("testProp(obj);");
CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value());
CHECK_EQ(96, named_access_count);
// Force the call inline cache into dictionary probing mode.
CompileRun("o.f = function() {}; testCallNormal(o)");
// Test that the named access check is still called for each
// invocation of the function.
value = CompileRun("testCallNormal(obj)");
CHECK_EQ(106, named_access_count);
context1->Exit(); context1->Exit();
context0->Exit(); context0->Exit();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment