1 package org.robolectric.res.android; 2 3 // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp 4 // and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/include/androidfw/ResourceTypes.h 5 6 import static org.robolectric.res.android.Errors.BAD_TYPE; 7 import static org.robolectric.res.android.Errors.NAME_NOT_FOUND; 8 import static org.robolectric.res.android.Errors.NO_ERROR; 9 import static org.robolectric.res.android.Errors.NO_INIT; 10 import static org.robolectric.res.android.ResourceString.decodeString; 11 import static org.robolectric.res.android.ResourceTypes.validate_chunk; 12 import static org.robolectric.res.android.Util.ALOGI; 13 import static org.robolectric.res.android.Util.ALOGW; 14 import static org.robolectric.res.android.Util.SIZEOF_INT; 15 import static org.robolectric.res.android.Util.isTruthy; 16 17 import java.lang.ref.WeakReference; 18 import java.nio.ByteBuffer; 19 import java.nio.ByteOrder; 20 import java.util.Objects; 21 import org.robolectric.res.android.ResourceTypes.ResChunk_header; 22 import org.robolectric.res.android.ResourceTypes.ResStringPool_header; 23 import org.robolectric.res.android.ResourceTypes.ResStringPool_header.Writer; 24 import org.robolectric.res.android.ResourceTypes.ResStringPool_ref; 25 import org.robolectric.res.android.ResourceTypes.ResStringPool_span; 26 import org.robolectric.res.android.ResourceTypes.WithOffset; 27 28 /** 29 * Convenience class for accessing data in a ResStringPool resource. 30 */ 31 @SuppressWarnings("NewApi") 32 public class ResStringPool { 33 34 private static boolean kDebugStringPoolNoisy = false; 35 36 private final long myNativePtr; 37 38 private int mError; 39 40 byte[] mOwnedData; 41 //private Object mOwnedData; 42 43 private ResStringPool_header mHeader; 44 private int mSize; 45 // private mutable Mutex mDecodeLock; 46 // const uint32_t* mEntries; 47 private IntArray mEntries; 48 // const uint32_t* mEntryStyles; 49 private IntArray mEntryStyles; 50 // const void* mStrings; 51 private int mStrings; 52 //private List<String> mStrings; 53 //private String[] mCache; 54 //private char16_t mutable** mCache; 55 private int mStringPoolSize; // number of uint16_t 56 // const uint32_t* mStyles; 57 private int mStyles; 58 private int mStylePoolSize; // number of int 59 ResStringPool()60 public ResStringPool() { 61 mError = NO_INIT; 62 myNativePtr = Registries.NATIVE_STRING_POOLS.register(new WeakReference<>(this)); 63 } 64 65 @Override finalize()66 protected void finalize() throws Throwable { 67 Registries.NATIVE_STRING_POOLS.unregister(myNativePtr); 68 } 69 getNativePtr()70 public long getNativePtr() { 71 return myNativePtr; 72 } 73 getNativeObject(long nativeId)74 public static ResStringPool getNativeObject(long nativeId) { 75 return Registries.NATIVE_STRING_POOLS.getNativeObject(nativeId).get(); 76 } 77 78 static class IntArray extends WithOffset { IntArray(ByteBuffer buf, int offset)79 IntArray(ByteBuffer buf, int offset) { 80 super(buf, offset); 81 } 82 get(int idx)83 int get(int idx) { 84 return myBuf().getInt(myOffset() + idx * SIZEOF_INT); 85 } 86 } 87 setToEmpty()88 void setToEmpty() 89 { 90 uninit(); 91 92 ByteBuffer buf = ByteBuffer.allocate(16 * 1024).order(ByteOrder.LITTLE_ENDIAN); 93 Writer resStringPoolWriter = new Writer(); 94 resStringPoolWriter.write(buf); 95 mOwnedData = new byte[buf.position()]; 96 buf.position(); 97 buf.get(mOwnedData); 98 99 ResStringPool_header header = new ResStringPool_header(buf, 0); 100 mSize = 0; 101 mEntries = null; 102 mStrings = 0; 103 mStringPoolSize = 0; 104 mEntryStyles = null; 105 mStyles = 0; 106 mStylePoolSize = 0; 107 mHeader = header; 108 } 109 110 // status_t setTo(const void* data, size_t size, bool copyData=false); setTo(ByteBuffer buf, int offset, int size, boolean copyData)111 public int setTo(ByteBuffer buf, int offset, int size, boolean copyData) { 112 if (!isTruthy(buf) || !isTruthy(size)) { 113 return (mError=BAD_TYPE); 114 } 115 116 uninit(); 117 118 // The chunk must be at least the size of the string pool header. 119 if (size < ResStringPool_header.SIZEOF) { 120 ALOGW("Bad string block: data size %d is too small to be a string block", size); 121 return (mError=BAD_TYPE); 122 } 123 124 // The data is at least as big as a ResChunk_header, so we can safely validate the other 125 // header fields. 126 // `data + size` is safe because the source of `size` comes from the kernel/filesystem. 127 if (validate_chunk(new ResChunk_header(buf, offset), ResStringPool_header.SIZEOF, 128 size, 129 "ResStringPool_header") != NO_ERROR) { 130 ALOGW("Bad string block: malformed block dimensions"); 131 return (mError=BAD_TYPE); 132 } 133 134 // final boolean notDeviceEndian = htods((short) 0xf0) != 0xf0; 135 // 136 // if (copyData || notDeviceEndian) { 137 // mOwnedData = data; 138 // if (mOwnedData == null) { 139 // return (mError=NO_MEMORY); 140 // } 141 //// memcpy(mOwnedData, data, size); 142 // data = mOwnedData; 143 // } 144 145 // The size has been checked, so it is safe to read the data in the ResStringPool_header 146 // data structure. 147 mHeader = new ResStringPool_header(buf, offset); 148 149 // if (notDeviceEndian) { 150 // ResStringPool_header h = final_cast<ResStringPool_header*>(mHeader); 151 // h.header.headerSize = dtohs(mHeader.header.headerSize); 152 // h.header.type = dtohs(mHeader.header.type); 153 // h.header.size = dtohl(mHeader.header.size); 154 // h.stringCount = dtohl(mHeader.stringCount); 155 // h.styleCount = dtohl(mHeader.styleCount); 156 // h.flags = dtohl(mHeader.flags); 157 // h.stringsStart = dtohl(mHeader.stringsStart); 158 // h.stylesStart = dtohl(mHeader.stylesStart); 159 // } 160 161 if (mHeader.header.headerSize > mHeader.header.size 162 || mHeader.header.size > size) { 163 ALOGW("Bad string block: header size %d or total size %d is larger than data size %d\n", 164 (int)mHeader.header.headerSize, (int)mHeader.header.size, (int)size); 165 return (mError=BAD_TYPE); 166 } 167 mSize = mHeader.header.size; 168 mEntries = new IntArray(mHeader.myBuf(), mHeader.myOffset() + mHeader.header.headerSize); 169 170 if (mHeader.stringCount > 0) { 171 if ((mHeader.stringCount*4 /*sizeof(uint32_t)*/ < mHeader.stringCount) // uint32 overflow? 172 || (mHeader.header.headerSize+(mHeader.stringCount*4 /*sizeof(uint32_t)*/)) 173 > size) { 174 ALOGW("Bad string block: entry of %d items extends past data size %d\n", 175 (int)(mHeader.header.headerSize+(mHeader.stringCount*4/*sizeof(uint32_t)*/)), 176 (int)size); 177 return (mError=BAD_TYPE); 178 } 179 180 int charSize; 181 if (isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)) { 182 charSize = 1 /*sizeof(uint8_t)*/; 183 } else { 184 charSize = 2 /*sizeof(uint16_t)*/; 185 } 186 187 // There should be at least space for the smallest string 188 // (2 bytes length, null terminator). 189 if (mHeader.stringsStart >= (mSize - 2 /*sizeof(uint16_t)*/)) { 190 ALOGW("Bad string block: string pool starts at %d, after total size %d\n", 191 (int)mHeader.stringsStart, (int)mHeader.header.size); 192 return (mError=BAD_TYPE); 193 } 194 195 mStrings = mHeader.stringsStart; 196 197 if (mHeader.styleCount == 0) { 198 mStringPoolSize = (mSize - mHeader.stringsStart) / charSize; 199 } else { 200 // check invariant: styles starts before end of data 201 if (mHeader.stylesStart >= (mSize - 2 /*sizeof(uint16_t)*/)) { 202 ALOGW("Bad style block: style block starts at %d past data size of %d\n", 203 (int)mHeader.stylesStart, (int)mHeader.header.size); 204 return (mError=BAD_TYPE); 205 } 206 // check invariant: styles follow the strings 207 if (mHeader.stylesStart <= mHeader.stringsStart) { 208 ALOGW("Bad style block: style block starts at %d, before strings at %d\n", 209 (int)mHeader.stylesStart, (int)mHeader.stringsStart); 210 return (mError=BAD_TYPE); 211 } 212 mStringPoolSize = 213 (mHeader.stylesStart-mHeader.stringsStart)/charSize; 214 } 215 216 // check invariant: stringCount > 0 requires a string pool to exist 217 if (mStringPoolSize == 0) { 218 ALOGW("Bad string block: stringCount is %d but pool size is 0\n", (int)mHeader.stringCount); 219 return (mError=BAD_TYPE); 220 } 221 222 // if (notDeviceEndian) { 223 // int i; 224 // uint32_t* e = final_cast<uint32_t*>(mEntries); 225 // for (i=0; i<mHeader.stringCount; i++) { 226 // e[i] = dtohl(mEntries[i]); 227 // } 228 // if (!(mHeader.flags&ResStringPool_header::UTF8_FLAG)) { 229 // final uint16_t* strings = (final uint16_t*)mStrings; 230 // uint16_t* s = final_cast<uint16_t*>(strings); 231 // for (i=0; i<mStringPoolSize; i++) { 232 // s[i] = dtohs(strings[i]); 233 // } 234 // } 235 // } 236 237 // if ((mHeader->flags&ResStringPool_header::UTF8_FLAG && 238 // ((uint8_t*)mStrings)[mStringPoolSize-1] != 0) || 239 // (!(mHeader->flags&ResStringPool_header::UTF8_FLAG) && 240 // ((uint16_t*)mStrings)[mStringPoolSize-1] != 0)) { 241 242 if ((isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG) 243 && (mHeader.getByte(mStrings + mStringPoolSize - 1) != 0)) 244 || (!isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG) 245 && (mHeader.getShort(mStrings + mStringPoolSize * 2 - 2) != 0))) { 246 ALOGW("Bad string block: last string is not 0-terminated\n"); 247 return (mError=BAD_TYPE); 248 } 249 } else { 250 mStrings = -1; 251 mStringPoolSize = 0; 252 } 253 254 if (mHeader.styleCount > 0) { 255 mEntryStyles = new IntArray(mEntries.myBuf(), mEntries.myOffset() + mHeader.stringCount * SIZEOF_INT); 256 // invariant: integer overflow in calculating mEntryStyles 257 if (mEntryStyles.myOffset() < mEntries.myOffset()) { 258 ALOGW("Bad string block: integer overflow finding styles\n"); 259 return (mError=BAD_TYPE); 260 } 261 262 // if (((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader) > (int)size) { 263 if ((mEntryStyles.myOffset() - mHeader.myOffset()) > (int)size) { 264 ALOGW( 265 "Bad string block: entry of %d styles extends past data size %d\n", 266 (int) mEntryStyles.myOffset(), (int) size); 267 return (mError=BAD_TYPE); 268 } 269 mStyles = mHeader.stylesStart; 270 if (mHeader.stylesStart >= mHeader.header.size) { 271 ALOGW("Bad string block: style pool starts %d, after total size %d\n", 272 (int)mHeader.stylesStart, (int)mHeader.header.size); 273 return (mError=BAD_TYPE); 274 } 275 mStylePoolSize = 276 (mHeader.header.size-mHeader.stylesStart) /* / sizeof(uint32_t)*/; 277 278 // if (notDeviceEndian) { 279 // size_t i; 280 // uint32_t* e = final_cast<uint32_t*>(mEntryStyles); 281 // for (i=0; i<mHeader.styleCount; i++) { 282 // e[i] = dtohl(mEntryStyles[i]); 283 // } 284 // uint32_t* s = final_cast<uint32_t*>(mStyles); 285 // for (i=0; i<mStylePoolSize; i++) { 286 // s[i] = dtohl(mStyles[i]); 287 // } 288 // } 289 290 // final ResStringPool_span endSpan = { 291 // { htodl(ResStringPool_span.END) }, 292 // htodl(ResStringPool_span.END), htodl(ResStringPool_span.END) 293 // }; 294 // if (memcmp(&mStyles[mStylePoolSize-(sizeof(endSpan)/sizeof(uint32_t))], 295 // &endSpan, sizeof(endSpan)) != 0) { 296 ResStringPool_span endSpan = new ResStringPool_span(buf, 297 mHeader.myOffset() + mStyles + (mStylePoolSize - ResStringPool_span.SIZEOF /* / 4 */)); 298 if (!endSpan.isEnd()) { 299 ALOGW("Bad string block: last style is not 0xFFFFFFFF-terminated\n"); 300 return (mError=BAD_TYPE); 301 } 302 } else { 303 mEntryStyles = null; 304 mStyles = 0; 305 mStylePoolSize = 0; 306 } 307 308 return (mError=NO_ERROR); 309 } 310 311 // public void setTo(XmlResStringPool xmlStringPool) { 312 // this.mHeader = new ResStringPoolHeader(); 313 // this.mStrings = new ArrayList<>(); 314 // Collections.addAll(mStrings, xmlStringPool.strings()); 315 // } 316 setError(int error)317 private int setError(int error) { 318 mError = error; 319 return mError; 320 } 321 uninit()322 void uninit() { 323 setError(NO_INIT); 324 mHeader = null; 325 } 326 stringAt(int idx)327 public String stringAt(int idx) { 328 if (mError == NO_ERROR && idx < mHeader.stringCount) { 329 final boolean isUTF8 = (mHeader.flags&ResStringPool_header.UTF8_FLAG) != 0; 330 // const uint32_t off = mEntries[idx]/(isUTF8?sizeof(uint8_t):sizeof(uint16_t)); 331 ByteBuffer buf = mHeader.myBuf(); 332 int bufOffset = mHeader.myOffset(); 333 // const uint32_t off = mEntries[idx]/(isUTF8?sizeof(uint8_t):sizeof(uint16_t)); 334 final int off = mEntries.get(idx) 335 /(isUTF8?1/*sizeof(uint8_t)*/:2/*sizeof(uint16_t)*/); 336 if (off < (mStringPoolSize-1)) { 337 if (!isUTF8) { 338 final int strings = mStrings; 339 final int str = strings+off*2; 340 return decodeString(buf, bufOffset + str, ResourceString.Type.UTF16); 341 // int u16len = decodeLengthUTF16(buf, bufOffset + str); 342 // if ((str+u16len*2-strings) < mStringPoolSize) { 343 // // Reject malformed (non null-terminated) strings 344 // if (buf.getShort(bufOffset + str + u16len*2) != 0x0000) { 345 // ALOGW("Bad string block: string #%d is not null-terminated", 346 // (int)idx); 347 // return null; 348 // } 349 // byte[] bytes = new byte[u16len * 2]; 350 // buf.position(bufOffset + str); 351 // buf.get(bytes); 352 // // Reject malformed (non null-terminated) strings 353 // if (str[encLen] != 0x00) { 354 // ALOGW("Bad string block: string #%d is not null-terminated", 355 // (int)idx); 356 // return NULL; 357 // } 358 // return new String(bytes, StandardCharsets.UTF_16); 359 // } else { 360 // ALOGW("Bad string block: string #%d extends to %d, past end at %d\n", 361 // (int)idx, (int)(str+u16len-strings), (int)mStringPoolSize); 362 // } 363 } else { 364 final int strings = mStrings; 365 final int u8str = strings+off; 366 return decodeString(buf, bufOffset + u8str, ResourceString.Type.UTF8); 367 368 // *u16len = decodeLength(&u8str); 369 // size_t u8len = decodeLength(&u8str); 370 // 371 // // encLen must be less than 0x7FFF due to encoding. 372 // if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) { 373 // AutoMutex lock(mDecodeLock); 374 // 375 // if (mCache != NULL && mCache[idx] != NULL) { 376 // return mCache[idx]; 377 // } 378 // 379 // // Retrieve the actual length of the utf8 string if the 380 // // encoded length was truncated 381 // if (stringDecodeAt(idx, u8str, u8len, &u8len) == NULL) { 382 // return NULL; 383 // } 384 // 385 // // Since AAPT truncated lengths longer than 0x7FFF, check 386 // // that the bits that remain after truncation at least match 387 // // the bits of the actual length 388 // ssize_t actualLen = utf8_to_utf16_length(u8str, u8len); 389 // if (actualLen < 0 || ((size_t)actualLen & 0x7FFF) != *u16len) { 390 // ALOGW("Bad string block: string #%lld decoded length is not correct " 391 // "%lld vs %llu\n", 392 // (long long)idx, (long long)actualLen, (long long)*u16len); 393 // return NULL; 394 // } 395 // 396 // utf8_to_utf16(u8str, u8len, u16str, *u16len + 1); 397 // 398 // if (mCache == NULL) { 399 // #ifndef __ANDROID__ 400 // if (kDebugStringPoolNoisy) { 401 // ALOGI("CREATING STRING CACHE OF %zu bytes", 402 // mHeader->stringCount*sizeof(char16_t**)); 403 // } 404 // #else 405 // // We do not want to be in this case when actually running Android. 406 // ALOGW("CREATING STRING CACHE OF %zu bytes", 407 // static_cast<size_t>(mHeader->stringCount*sizeof(char16_t**))); 408 // #endif 409 // mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t*)); 410 // if (mCache == NULL) { 411 // ALOGW("No memory trying to allocate decode cache table of %d bytes\n", 412 // (int)(mHeader->stringCount*sizeof(char16_t**))); 413 // return NULL; 414 // } 415 // } 416 // *u16len = (size_t) actualLen; 417 // char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t)); 418 // if (!u16str) { 419 // ALOGW("No memory when trying to allocate decode cache for string #%d\n", 420 // (int)idx); 421 // return NULL; 422 // } 423 // 424 // if (kDebugStringPoolNoisy) { 425 // ALOGI("Caching UTF8 string: %s", u8str); 426 // } 427 // 428 // mCache[idx] = u16str; 429 // return u16str; 430 // } else { 431 // ALOGW("Bad string block: string #%lld extends to %lld, past end at %lld\n", 432 // (long long)idx, (long long)(u8str+u8len-strings), 433 // (long long)mStringPoolSize); 434 // } 435 } 436 } else { 437 ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n", 438 (int)idx, (int)(off*2/*sizeof(uint16_t)*/), 439 (int)(mStringPoolSize*2/*sizeof(uint16_t)*/)); 440 } 441 } 442 return null; 443 } 444 stringAt(int idx, Ref<Integer> outLen)445 String stringAt(int idx, Ref<Integer> outLen) { 446 String s = stringAt(idx); 447 if (s != null && outLen != null) { 448 outLen.set(s.length()); 449 } 450 return s; 451 } 452 string8At(int id, Ref<Integer> outLen)453 public String string8At(int id, Ref<Integer> outLen) { 454 return stringAt(id, outLen); 455 } 456 styleAt(final ResStringPool_ref ref)457 final ResStringPool_span styleAt(final ResStringPool_ref ref) { 458 return styleAt(ref.index); 459 } 460 styleAt(int idx)461 public final ResStringPool_span styleAt(int idx) { 462 if (mError == NO_ERROR && idx < mHeader.styleCount) { 463 // const uint32_t off = (mEntryStyles[idx]/sizeof(uint32_t)); 464 final int off = mEntryStyles.get(idx) / SIZEOF_INT; 465 if (off < mStylePoolSize) { 466 // return (const ResStringPool_span*)(mStyles+off); 467 return new ResStringPool_span( 468 mHeader.myBuf(), mHeader.myOffset() + mStyles + off * SIZEOF_INT); 469 } else { 470 ALOGW("Bad string block: style #%d entry is at %d, past end at %d\n", 471 (int)idx, (int)(off*SIZEOF_INT), 472 (int)(mStylePoolSize*SIZEOF_INT)); 473 } 474 } 475 return null; 476 } 477 indexOfString(String str)478 public int indexOfString(String str) { 479 if (mError != NO_ERROR) { 480 return mError; 481 } 482 483 if (kDebugStringPoolNoisy) { 484 ALOGI("indexOfString : %s", str); 485 } 486 487 if ( (mHeader.flags&ResStringPoolHeader.SORTED_FLAG) != 0) { 488 // Do a binary search for the string... this is a little tricky, 489 // because the strings are sorted with strzcmp16(). So to match 490 // the ordering, we need to convert strings in the pool to UTF-16. 491 // But we don't want to hit the cache, so instead we will have a 492 // local temporary allocation for the conversions. 493 int l = 0; 494 int h = mHeader.stringCount-1; 495 496 int mid; 497 while (l <= h) { 498 mid = l + (h - l)/2; 499 String s = stringAt(mid); 500 int c = s != null ? s.compareTo(str) : -1; 501 if (kDebugStringPoolNoisy) { 502 ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n", 503 s, c, (int)l, (int)mid, (int)h); 504 } 505 if (c == 0) { 506 if (kDebugStringPoolNoisy) { 507 ALOGI("MATCH!"); 508 } 509 return mid; 510 } else if (c < 0) { 511 l = mid + 1; 512 } else { 513 h = mid - 1; 514 } 515 } 516 } else { 517 // It is unusual to get the ID from an unsorted string block... 518 // most often this happens because we want to get IDs for style 519 // span tags; since those always appear at the end of the string 520 // block, start searching at the back. 521 for (int i = mHeader.stringCount; i>=0; i--) { 522 String s = stringAt(i); 523 if (kDebugStringPoolNoisy) { 524 ALOGI("Looking at %s, i=%d\n", s, i); 525 } 526 if (Objects.equals(s, str)) { 527 if (kDebugStringPoolNoisy) { 528 ALOGI("MATCH!"); 529 } 530 return i; 531 } 532 } 533 } 534 535 return NAME_NOT_FOUND; 536 } 537 // size()538 public int size() { 539 return mError == NO_ERROR ? mHeader.stringCount : 0; 540 } 541 styleCount()542 int styleCount() { 543 return mError == NO_ERROR ? mHeader.styleCount : 0; 544 } 545 bytes()546 int bytes() { 547 return mError == NO_ERROR ? mHeader.header.size : 0; 548 } 549 isUTF8()550 public boolean isUTF8() { 551 return true; 552 } 553 getError()554 public int getError() { 555 return mError; 556 } 557 558 // int styleCount() final; 559 // int bytes() final; 560 // 561 // boolean isSorted() final; 562 // boolean isUTF8() final; 563 // 564 565 } 566